一般化 した FizzBuzz を Squeak Smalltalk で

お題の趣旨をはげしく取り違えていることはご容赦いただくとして、3 に対する Fizz、5 に対する Buzz というように、数値とその倍数の時に置き換える語の任意のペアを与えられるように、メソッド #replaceAllMultiplesAccordingTo: を定義してみます。

SequenceableCollection >> replaceAllMultiplesAccordingTo: numAndWordPairs
   | fizzBuzzString |
   ^self collect: [:int |
      fizzBuzzString := String streamContents: [:ss |
         numAndWordPairs pairsDo: [:divNum :repWord |
            (int isDivisibleBy: divNum) ifTrue: [ss nextPutAll: repWord]]].
      doBlock value: (fizzBuzzString ifEmpty: [int] ifNotEmpty: [fizzBuzzString])]


使い方はこんなかんじ。

(1 to: 15) replaceAllMultiplesAccordingTo: #(3 Fizz 5 Buzz)
=>  #(1 2 'Fizz' 4 'Buzz' 'Fizz' 7 8 'Fizz' 'Buzz' 11 'Fizz' 13 14 'FizzBuzz')


ただ、レシーバと同じ要素数のコレクションがいちいち生成されるのなんとなくイヤ〜ンな感じなので、各要素について必要な処理をブロックとして与えられるブロック(クロージャ)メソッド #replaceAllMultiplesAccordingTo:thenDo: として定義し直します。

SequenceableCollection >> replaceAllMultiplesAccordingTo: numAndWordPairs thenDo: doBlock
   | fizzBuzzString |
   self do: [:int |
      fizzBuzzString := String streamContents: [:ss |
         numAndWordPairs pairsDo: [:divNum :repWord |
            (int isDivisibleBy: divNum) ifTrue: [ss nextPutAll: repWord]]].
      doBlock value: (fizzBuzzString ifEmpty: [int] ifNotEmpty: [fizzBuzzString])]


これを用いて、先の #replaceAllMultiplesAccordingTo: はこんなふうに書き換えられます。

SequenceableCollection >> replaceAllMultiplesAccordingTo: numAndWordPairs
   | result |
   result := OrderedCollection new.
   self replaceAllMultiplesAccordingTo: numAndWordPairs thenDo: [:each | result add: each].
   ^result


たとえば、偶数を Hoge、3 の倍数を Hoge、5 の倍数を Huga と置き換えつつ、結果をトランスクリプトに行を改めつつ出力したいときは、次のように書けます。

World findATranscript: nil.
(1 to: 30)
   replaceAllMultiplesAccordingTo: #(2 Hage 3 Hoge 5 Huga)
   thenDo: [:each | Transcript cr; show: each]
1
Hage
Hoge
Hage
Huga
HageHoge
7
Hage
Hoge
HageHuga
11
HageHoge
13
Hage
HogeHuga
Hage
17
HageHoge
19
HageHuga
Hoge
Hage
23
HageHoge
Huga
Hage
Hoge
Hage
29
HageHogeHuga