FizzBuzzコードゴルフ

お久しぶりです、和田です。

本日はFizzBuzzとコードゴルフについて紹介です。

FizzBuzz?

FizzBuzzとは、

最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」(Bizz Buzzの場合は「Bizz」)、5で割り切れる場合は「Buzz」、両者で割り切れる場合(すなわち15で割り切れる場合)は「Fizz Buzz」(Bizz Buzzの場合は「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

引用 - Wikipedia

という英語圏の遊びですね。

つまり以下のようにゲームは進んでいきます。

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14 FizzBuzz, 16...

FizzBuzzはプログラムの基礎的な要素(ループ、if、メソッド化などなど)が含まれているため、初めてのプログラム言語や初学者への問題として使われることがあるようです。

また、今回のように競技プログラミングの目的でも使われることもあります。

コードゴルフ?

コードゴルフとは、あるお題を出来る限り少ない文字数で実現する競技プログラミングのことです。

ゴルフのルールと同じく、少ないほうが優れているとみなされるプログラミングのため、この名称が付けられたようです。

有名なものだと7行テトリスなどがあります。

メガデモも、ある種コードゴルフと言えるかもしれません。

コード

さて、今回の本題「1から100までのFizzBuzzをコードゴルフする」です。JavaScriptとRubyで順を追って書いてみました。短さは……残念ですが(笑)

また、私が調べた中で最小のコードも同時に掲載します。

先に進む前にご自分で書かれてから続きを読むのも面白いかもしれません。

JavaScript

まず、JavaScriptです。動作確認環境ですが、Chrome 51で行いました。

以下のコードからスタートしました。

// 222 bytes
for(var i = 1; i <= 100; i++) {  
  if(i % 15 === 0) {
    console.log('fizzbuzz');
  } else if(i % 3 === 0) {
    console.log('fizz');
  } else if(i % 5 === 0) {
    console.log('buzz');
  } else {
    console.log(i);
  }
}

何の変哲もないコードですね。

次に基本中の基本、変数宣言の var を削除します。

// 218 bytes
for(i = 1; i <= 100; i++) {  
  if(i % 15 === 0) {
    console.log('fizzbuzz');
  } else if(i % 3 === 0) {
    console.log('fizz');
  } else if(i % 5 === 0) {
    console.log('buzz');
  } else {
    console.log(i);
  }
}

続いて、比較演算子をを変更しました。

具体的には、
<=< に、
===== にしました。

また、あわせてセミコロンを削除しています。

// 210 bytes
for(i = 1; i < 101; i++) {  
  if(i % 15 == 0) {
    console.log('fizzbuzz')
  } else if(i % 3 == 0) {
    console.log('fizz')
  } else if(i % 5 == 0) {
    console.log('buzz')
  } else {
    console.log(i)
  }
}

まだここまでは、短くなっているものの、コードゴルフ感がありませんが、ここからそれっぽくなっていきます。

ここでは合計4回出ている console.log を短くしようとしています。

// 202 bytes
c = console  
c.l = c.log  
for(i = 1; i < 101; i++) {  
  if(i % 15 == 0) {
    c.l('fizzbuzz')
  } else if(i % 3 == 0) {
    c.l('fizz')
  } else if(i % 5 == 0) {
    c.l('buzz')
  } else {
    c.l(i)
  }
}

210バイトから202バイトなので、一応短くなっています。

ちなみにChrome 51では動作しませんでしたが、Node v6.2.2では以下のコードが動作しました。

Nodeでやればもっと短くできるみたいですね。

// 186 bytes
c = console.log  
for(i = 1; i < 101; i++) {  
  if(i % 15 == 0) {
    c('fizzbuzz')
  } else if(i % 3 == 0) {
    c('fizz')
  } else if(i % 5 == 0) {
    c('buzz')
  } else {
    c(i)
  }
}

次に、こちらも4回出ている if-else を削除します。

実務で使われたら確実に書きなおすであろう、可読性最悪の三項演算子の入れ子です。

// 147 bytes
c = console  
c.l = c.log  
for(i = 1; i < 101; i++) {  
  i % 15 == 0 ? c.l('fizzbuzz') : i % 3 == 0 ? c.l('fizz') : i % 5 == 0 ? c.l('buzz') : c.l(i)
}

自分が考えた中では最小だ!となったのでスペースと改行を削除します。

この際、削除したセミコロンが復活します。

// 106 bytes
c=console;c.l=c.log;for(i=1;i<101;i++)i%15==0?c.l('fizzbuzz'):i%3==0?c.l('fizz'):i%5==0?c.l('buzz'):c.l(i)  

……短くない!100バイト切りたいなーと。fizzbuzzっていう文字列をなくしたいなーと、うんうん唸って考えました。だがしかし、ここでギブアップ。。完全敗北。

しかし、100バイトはやはり切りたかったため、模範解答となるような最小コードをググッた結果、コードゴルフ界の有名人、Ozy氏によるコードが見つかりました。以下がそのコードです。

なお、Ozy氏のコードは標準出力に print を使用していたため、その部分のみ console.log に書き換えています。そのためオリジナルのコードよりバイト数が増えてしまっている点はご留意ください。

// http://d.hatena.ne.jp/Ozy/20070225#p1
for(i=0;++i<101;console.log(i%5?x||i:x+'Buzz'))x=i%3?'':'Fizz'  

ふむふむ、なるほどなるほど。わかりづらい!minifyを解除してみよう。

for(i = 0; ++i < 101; console.log(i % 5 ? x || i : x + 'Buzz'))  
  x = i % 3 ? '' : 'Fizz'

なるほど、forでループをもちろん回していますが、終了条件で i をインクリメントしてしまっていますね。これで継続条件が不必要になり、その継続条件で標準出力をしてしまっているわけですね。

また、JavaScriptの 0 == false という性質をうまいこと利用していますね。これはとても参考になる…。

というわけで、Ozy氏のコードを参考にしつつ、自分のコードを改修した結果が以下になります。ご確認ください。

// 64 bytes
for(i=0;++i<101;)console.log((i%3?'':'fizz')+(i%5?'':'buzz')||i)  

Ozy氏のコードと比べると '' が2度出てきてしまっている点と、文字列連結にさせるため () で囲む必要が出てきてしまっているのが2バイト多い原因だと思います。

ですが僕はとても満足です。

Ruby

次にRubyです。

「JavaScriptで結構短く出来たし、これ参考にすればいい感じにできんだろ」とか思っていましたが、勝手が違いました。。

まず、基点となるコードです。

# 152 bytes
(1..100).each { |i|
  if i % 15 == 0
    puts 'fizzbuzz'
  elsif i % 3 == 0
    puts 'fizz'
  elsif i % 5 == 0
    puts 'buzz'
  else
    puts i
  end
}

「お前のコードにはなんの面白みもない」と言われちゃうくらいJavaScriptとおんなじようなコードです。

ここから頑張ります。

# 121 bytes
(1..100).each { |i|
  s = 'fizz' if i % 3 == 0
  s = 'buzz' if i % 5 == 0
  s = 'fizzbuzz' if i % 15 == 0
  puts s || i
}

後置きifを使いました。

普通の if と違うのは、判定の順番ですね。

# 109 bytes
(1..100).each { |i|
  s = ''
  s = 'fizz' if i % 3 == 0
  s << 'buzz' if i % 5 == 0
  puts s.empty? ? i : s
}

fizzbuzz という文字列をなくすために変数への代入を試みました。

s = '' がないと s << buzz の時に怒られます。

そしてこれ以上思いつかないという残念な結果に…。

仕方が無いのでスペースと改行を削除します。

# 79 bytes
(1..100).each{|i|s='';s='fizz'if i%3==0;s<<'buzz'if i%5==0;puts s.empty? ? i:s}

うん、まぁ、こんなもんだよね(諦観)

よし、見つけた最小コードを見てみよう。

# http://qiita.com/zakuroishikuro/items/151167640fcd909d0c47
1.upto(100){|n|puts'FizzBuzz  
'[i=n**4%-15,i+13]||n}  

は?ちょっと待って、意味がわからない。解説読んでも何言ってるかわからない。。。

ここは、最小コードではないですがOzy氏に再登板していただこう…。

# http://d.hatena.ne.jp/Ozy/20070225#p1
1.upto(?d){|i|v=i%3<1?"Fizz":"";puts i%5<1?v+"Buzz":v>""?v:i}  

なるほど、 [Integer#upto](http://docs.ruby-lang.org/ja/1.9.3/class/Integer.html#I_UPTO) というのがあるのですね。

また比較演算子は ==0 を使わず <1 を使っていますね。なるほどなぁ…。

というわけでOzy氏の上記コードを参考に、自分のコードを修正しました!

# 75 bytes
1.upto(100){|i|s='';s='fizz'if i%3<1;s<<'buzz'if i%5<1;puts s.empty? ? i:s}  

ほぼ変わらんじゃないか!根底から変えないとダメみたいですね。


今回は JavaScript と Ruby のみの紹介となりましたが、他の言語で挑戦すると色々と知見が得られそうな感じがします。

以上、FizzBuzzでコードゴルフでした!

 

 

あゆた社内でも競技プログラミングしたいなー (チラッチラッ