FizzBuzzのしくみ

id:yoosaki:20070510#p1
いくら見ても意味がわからないので、irbで動かしてみた。
(コーディング環境はeclipseに移したけれど、こういうときに役に立つからEmacsが捨てられない)

肝はここ。

s=[[:Fizz][i%3],[:Buzz][i%5]]*''

:Fizz は 'Fizz' をシンボルで書いて一文字短縮したとわかる*1
わからんのが[:Fizz][i%3]と、最後の*''。
調べてみたら、RubyではArrayに文字列をかけるとArray#joinと同じ効果があるとわかった。
[1,2,3].join(';') = "1;2;3"
すなわち
[1,2,3]*';' = "1;2;3"
というわけ。

さて問題は[:Fizz][i%3]ですよ。
「配列と配列を並べて書くとどう動作する?」とか悩んでしまいましたがまったくの無駄で、[i%3]は[:Fizz]に対する添字です。

i%3はiのあまりなので、iが1,2,3,4,5,6,……と変化すると、i%3は1,2,0,1,2,0,……となる。
すなわち

[:Fizz][0] = :Fizz
[:Fizz][1] = nil
[:Fizz][2] = nil

というわけで、この式はiが3で割り切れるときのみ:Fizzを返すわけです。

Buzzについても同様で、肝の式の前半は

[nil, nil]
[:Fizz, nil]
[nil, :Buzz]
[:Fizz, :Buzz]

の4つの値をとりうることになる。
これを''でjoinするから、sの値は'', 'Fizz', 'Buzz', 'FizzBuzz'のいずれか。

てことはs[1]はnilか?iか?uになる。s[1]がnilのときだけiを返すため、最後はs[1]?s:iとなる寸法です。(s[0]を見ても可っぽい)

こういう遊びをコードゴルフというそうです。
プログラムの文字数を打数に見立て、文法知識と発想力を駆使して一文字でも短く書いて行くわけです。

ちなみにこれより短い56バイトの例がすでにここに。
id:yoosaki:20070516#p1
これはこれでまた読み込まないと意味がわからない!!

*1:厳密には文字列とシンボルは異なるけど、ここではその差異は無意味。さてASAくん、Rubyの文字列とシンボルの違いを説明できるかね?

もっともぬるいFizzBuzz

FizzBuzzというのは、「どうしてプログラマに・・・プログラムが書けないのか?*1」の記事に出てくる、プログラマ面接のテスト問題です。
1〜100の数字に対して、3で割り切れる数なら"Fizz"、5で割り切れる数なら"Buzz"、3と5の両方で割り切れる数なら"FizzBuzz"、どれにもあてはまらなければ単にその数字をプリントアウトするというもの。

これが書けないのは本当にプログラムを書いたことがないのだろう、という程度の問題なので、腕におぼえのある人は「いかに短いコードで書くか」を競う、コードゴルフのゲームに走るわけです。

しかし上のエントリにも書いたように、文法知識と発想力を駆使したコードは本物の呪文と化します(笑)
正攻法で書いたFizzBuzzのコードがあれば、コードゴルフの面白みが少しは伝わるでしょうか?

for i in 1..100
  if i%3 == 0
    print "Fizz"
  end
  if i%5 == 0
    print "Buzz"
  end
  unless i%3 == 0 or i%5 == 0
    print i
  end
  puts 
end

12行162バイト。たとえば1行目のfor i in 1..100を、(1..100).each とか、1.upto(100) とか書き換えるとちょっと短くなるわけです。
if文とunless文の連続はif ... elsif ... else と直したくなりますが、そうすると15の倍数でFizzBuzzが出ませんね。

この節を2行にまとめるのはわりと簡単です。

for i in 1..100
  s = "#{'Fizz' if i%3 == 0}#{'Buzz' if i%5 == 0}"
  puts (s == "")? i : s
end

埋め込み文字列と三項演算子でぐっと短くなりましたが、なにをやってるかはかなりわかりにくくなりました(笑)

もう一工夫するとこの節は1行にできます。

for i in 1..100
  puts ( (s = "#{'Fizz' if i%3 == 0}#{'Buzz' if i%5 == 0}") == "")? i : s
end

sを代入しながら空文字列判定しています。カッコが多くてわけわかりません。

がんばって全部1行にまとめてみました。

1.upto(100){|i|puts(((s="#{i%3==0&&:Fizz}#{i%5==0&&:Buzz}")=="")?i:s)}

動きそうですがダメです。Fizz(Buzz)にならない部分が全部Falseになってしまいます。

そろそろ眠いのでおしまい。これをやるにはRDEの方が便利かも。