一般化 した 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