BuzzFib をネタに Squeak Smalltalk で遊ぶ


自分としては FibBuzz より BuzzFib のほうがネタとしては断然おもしろいのでこちらを。

BuzzFib: 1から数を列挙していく.それがフィボナッチ数ならFib, 5で割り切れるならBuzz, その両方ならBuzzFib, その両方でもない場合はその数を出力してください


まずオーソドックスに処理を書いてみましょう。

| pair |
pair := #(0 1) copy.
(1 to: 55) collect: [:nn |
   | str |
   [pair last < nn]
      whileTrue: [pair swap: 1 with: 2; at: 2 incrementBy: pair first; first].
   str := ''.
   (nn isDivisibleBy: 5) ifTrue: [str := str, 'Buzz'].
   pair last = nn ifTrue: [str := str, 'Fib'].
   str ifEmpty: [nn]]
"=> #('Fib' 'Fib' 'Fib' 4 'BuzzFib' 6 7 'Fib' 9 'Buzz' 11 12 'Fib' 14 'Buzz' 16 17 18 19 'Buzz' 
'Fib' 22 23 24 'Buzz' 26 27 28 29 'Buzz' 31 32 33 'Fib' 'Buzz' 36 37 38 39 'Buzz' 41 42 43 44 'Buzz' 
46 47 48 49 'Buzz' 51 52 53 54 'BuzzFib') "


オーソドックスとか言いつつ、のっけから

pair swap: 1 with: 2; at: 2 incrementBy: pair first; first


とか分かりにくく書いてしまったので解説すると、Smalltalk では残念ながら Ruby とかでの

a, b = b, a


のような2変数の値の入れ替えが簡潔に書けないので、待避用の変数を使う代わりにこんなふうに書いてみました(待避用の変数を使うのはなんか負けた気がするので―)。#swap:with: は配列の要素の入れ替えのメソッドです。

| array |
array := #(1 2 3).
array swap: 2 with: 3.
^array   "=> #(1 3 2)


なおセミコロン(;)は Smalltalkシンタックスシュガーでひとつ前の式のレシーバーに続くメッセージを畳みかけるようにして送るためのものです。

obj message1; message2; message3


これは、

obj message1. obj message2. obj message3


と同じことです。つまり at: 2 incrementBy: pair first (と、その続きの first )は前の式のレシーバーである pair に送られます。この #at:incrementBy: は Ruby で #[]= と += の組み合わせ

a := [1,2,3].
a[1] += 5
a   #=> [1,7,3]


みたいな感じで、第一引数の位置にある要素に第二引数の数値を足してから代入(破壊的に置き換える)メソッドです。最後に前述の通りカスケードを使って pair に改めて first を送ることで pair の最初の値を式の結果として返しています(カスケード式の場合、返値は最後の式の評価値が使われ、それより前の式の返値は破棄される)。

まとめると件の式では、サイズ2の配列(pair)の要素を入れ替え、両要素を足した値を改めて第2要素にして第1要素の値を結果として返す―という操作になります。つまるところフィボナッチ数の生成ですね。

| pair |
pair := #(0 1) copy.
(1 to: 10) collect: [:each | pair swap: 1 with: 2; at: 2 incrementBy: pair first; first]
"=> #(1 1 2 3 5 8 13 21 34 55) "


余談ですが Smalltalk VM はスタックマシンなのでその挙動をうまく利用すると待避用変数を使わずに変数の入れ替えを書くこともできます

| a b |
a := 3. b := 4.
a := b flag: (b := a).
{a. b}   "=> #(4 3) "
| a b |
a := 0. b := 1.
(1 to: 10) collect: [:each | a := b flag: (b := a + b)]
"=> #(1 1 2 3 5 8 13 21 34 55) "


が、なんというかまあ、短くはあるのですがなんとなく残念な感じが漂いますね。ちなみにここでは #flag: を使っていますが、なにもしない(つまりメソッド本体が定義されていないただ self を返すだけの)1引数のメソッドならなんでもOKです。

負けた感じとか意味不明なことを言っていないで、素直に普通に待避用の変数を使えという話はもちろんあります。

| a b temp |
a := 3. b := 4.
temp := a. a := b. b := temp.
{a. b}   "=> #(4 3) "
| a b temp |
a := 0. b := 1.
(1 to: 10) collect: [:each | temp := a. a := b. b := temp + b. a]
"=> #(1 1 2 3 5 8 13 21 34 55) "


さらにどーでもいい話ですが、Squeak Smalltalk の初期のバージョンでは {a. b} := {b. a} のように書いて入れ替えができたのですがこの書式は廃止されてしまったのでおそらく今後も復活はないでしょう。


閑話休題

| pair |
pair := #(0 1) copy.
(1 to: 55) collect: [:nn |
   | str |
   [pair last < nn]
      whileTrue: [pair swap: 1 with: 2; at: 2 incrementBy: pair first; first].
   str := ''.
   (nn isDivisibleBy: 5) ifTrue: [str := str, 'Buzz'].
   pair last = nn ifTrue: [str := str, 'Fib'].
   str ifEmpty: [nn]]


さて。冒頭のこのコードをいろいろといじってみることにします。まず、フィボナッチ数の生成にクロージャー(ブロック)やジェネレーターを使ってみましょう。

▼フィボナッチ数をクロージャーに生成させる版
| fib next |
fib := [
   | pair |
   pair := #(0 1) copy.
   [pair swap: 1 with: 2; at: 2 incrementBy: pair first; first]
] value.

next := fib value.
(1 to: 55) collect: [:nn |
   | str |
   [next < nn] whileTrue: [next := fib value].
   str := ''.
   (nn isDivisibleBy: 5) ifTrue: [str := str, 'Buzz'].
   next = nn ifTrue: [str := str, 'Fib'].
   str ifEmpty: [nn]]
▼フィボナッチ数をジェネレーターに生成させる版
| fib |
fib := Generator on: [:gen |
   | pair |
   pair := #(0 1) copy.
   [gen yield: (pair swap: 1 with: 2; at: 2 incrementBy: pair first; first)] repeat].

fib reset.
(1 to: 55) collect: [:nn |
   | str |
   [fib peek < nn] whileTrue: [fib next].
   str := ''.
   (nn isDivisibleBy: 5) ifTrue: [str := str, 'Buzz'].
   fib peek = nn ifTrue: [str := str, 'Fib'].
   str ifEmpty: [nn]]


両者とも似た感じですが、#peek や #reset が使えるジェネレーター版のほうが繰り返して使い回そうとしたときにメリットがありそうですね。

| fib |
fib := Generator on: [:gen |
   | pair |
   pair := #(0 1) copy.
   [gen yield: (pair swap: 1 with: 2; at: 2 incrementBy: pair first; first)] repeat].

(1 to: 3) collect: [:each |
   fib reset.
   (1 to: 5) collect: [:nn |
      | str |
      [fib peek < nn] whileTrue: [fib next].
      str := ''.
      (nn isDivisibleBy: 5) ifTrue: [str := str, 'Buzz'].
      fib peek = nn ifTrue: [str := str, 'Fib'].
      str ifEmpty: [nn]]
]
"=> #(
   #('Fib' 'Fib' 'Fib' 4 'BuzzFib') 
   #('Fib' 'Fib' 'Fib' 4 'BuzzFib') 
   #('Fib' 'Fib' 'Fib' 4 'BuzzFib')
) "


ただまあ、繰り返すとなるとフィボナッチ数列はその都度生成するよりはキャッシュに保持しておいたほうが効率や使い勝手は良さそうなのでインスタンス特異的メソッドを使って、与えられた数を含むかどうか(つまりそれがフィボナッチ数かを)判断すると同時に、自らの長さが十分でなければ必要な長さまでその都度伸張する遅延なフィボナッチ数列を作ってこれを利用することにします。

…と思ったのですが、キリもいいので今日はここまで。(やっぱり、このまま続けます)

| fib |
fib := OrderedCollection new assureUniClass.
fib addAll: #(1 1).
fib class compile: 'includes: elem
   [self last < elem] whileTrue: [self add: (self last: 2) sum].
   ^self last = elem'.
Smalltalk at: #Fib put: fib


これで Fib includes: 5 とかすることで与えた数がフィボナッチ数かどうかを判定できます。このままでもいいのですが、ついでに Integer>>#isFib も定義してしまいましょう。

Integer >> isFib
   ^Fib includes: self


前述のとおりそのままです。

5 isFib   "=> true "
4 isFib   "=> false "


ただいったんこうして #isFib が出来てしまうと、このメソッドのためだけに Fib があるのがなんとなく気持ち悪くなってきたので、#isFib だけで完結させるためにちょっとトリッキーなことをしてみました。

Integer >> isFib
   | fib |
   fib := #(1 1).
   fib isArray ifTrue: [
      thisContext method literalAt: 1 put: fib asOrderedCollection.
      thisContext restart].
   [fib last < self] whileTrue: [fib add: (fib last: 2) sum].
   ^fib includes: self


なんという恐ろしいコードでしょう。w 普通の言語のユーザーはこのメソッドで何が起こっているか一生わからないでしょうね。^^; とりあえず 1)いったん計算したフィボナッチ数は保持、2)計算済み数列の保持にグローバル変数は使わない―という目的は達せられたのでここはこれでよしとします。


ともあれこの #isFib を使えば、個人的には最近お気に入りのトレイトを使ったパターンも簡単に書けそうです。というわけでこれをもってこのネタ、とりあえずここで〆といたします。

Trait named: #TBuzzFib uses: #() category: 'Traits-BuzzFib'

TBuzzFib >> fibb
   self asInteger isFib ifTrue: [^self asString, 'Fib']

TBuzzFib >> buzz
   (self asInteger isDivisibleBy: 5) ifTrue: [^self asString, 'Buzz']

TBuzzFib >> value
   ^self asString splitInteger last
Integer uses: TBuzzFib.
String uses: TBuzzFib.
(1 to: 55) collect: [:m | m buzz fibb value]
"=> #('Fib' 'Fib' 'Fib' 4 'BuzzFib' 6 7 'Fib' 9 'Buzz' 11 12 'Fib' 14 'Buzz' 16 17 18 19 'Buzz' 
'Fib' 22 23 24 'Buzz' 26 27 28 29 'Buzz' 31 32 33 'Fib' 'Buzz' 36 37 38 39 'Buzz' 41 42 43 44 'Buzz' 
46 47 48 49 'Buzz' 51 52 53 54 'BuzzFib') "