RubyでFizzBuzzしてみる。ただしif文は使わない。(3)

さてこのシリーズも3回目である。今までの実装を振り返ってみよう。

今までの実装

1回目

fizz = ["Fizz", "*", "*"]
buzz = ["Buzz", "*", "*", "*", "*"]
for i in 1..100 do
    s = fizz[i % 3] + buzz[i % 5]
    puts(s.sub("*", "").sub("*", i.to_s))
end

いかにもRuby初心者が書きそうなコードである。いやまだ初心者だけど。


2回目

for i in 1..100 do
    puts((["Fizz", "", ""][i % 3] + ["Buzz", "", "", "", ""][i % 5]).sub(/^$/, i.to_s))
end

かなりすっきりしたが、1行が長い。無理矢理っぽい。

今回の実装

for i in 1..100 do
    puts((["Fizz"][i % 3].to_s + ["Buzz"][i % 5].to_s).sub(/^$/, i.to_s))
end

14文字短縮できた。今回の実装の肝は、配列。Javaプログラマなら、配列といえばArrayIndexOutOfBoundsExceptionというくらい配列のインデックスに敏感になってしまうところだが、なんとRubyでは例外が発生しない。そう、範囲外のインデックスを指定しても処理が続くのである。
具体的に言うと、Rubyでは配列の範囲外を参照するとnilが返ってくる。
しかも、nilもオブジェクトであり、メソッドを持っていてto_sなんかが使えてしまうのである!Javaプログラマには驚きの展開。
てなわけで、配列の要素は1個にすることができた。3または5の倍数でない場合はnil.to_sつまり""が連結されことになる。
3の倍数または5の倍数または15の倍数の時は連結した文字列をそのまま出力し、3でも5でも割り切れない場合は""を数値に置換している。


そしてさらにforループをなくすと…

(1..100).each{|i| puts((["Fizz"][i % 3].to_s + ["Buzz"][i % 5].to_s).sub(/^$/, i.to_s))}

なんと1行で済んでしまった。forループが1行で書けるなんて!
ただ1行が長いけどね。
これ以上はもうどうにもならないでしょ!?