RubyKaigi2008 Golfコンペに参加

せっかくなので、RubyKaigi2008 Golfコンペに参加してきた!

ゴルフは可能な限り短いストローク数 (打数) でカップにボールを入れることを競うスポーツですが、Code Golfは可能な限り少ないストローク数 (打鍵数、バイト数) で要求仕様を満たすプログラムを作成する遊びです。

日本 Ruby 会議 2008 - RubyKaigi2008 Golfコンペ

そういうわけで、可読性も処理効率も知ったことではなく、とにかく短いコードにするのが目的である。

お題は二つあったのだが、ほかのプログラムを聞いていたら両方を解く時間が無くなってしまった。そこで、今回は複利計算問題に的を絞った。コードの改変履歴はGitに記録してあるので、変遷を振り返ってみる。

最初のコード(140byte)。短くすることは考えず、とにかく要求仕様を満たすことを考えた。未使用変数もあったりして、かなり雑。

STDIN.read.scan(/(\d+):(\d+):(\d+)/) { |a,b,c|
  x = b.to_i
  puts x
  (1...a.to_i).each{|i|
    puts x = (x * (1.0+c.to_f/100)).to_i
  }
}

次(86byte)。STDINを省略できること、String#splitで配列にした方が短くてすむことに気づいた。配列にしたことでto_iをmapでまとめられるようになり、さらにサイズ短縮。ついでに改行とスペースを削ってサイズを減らしている。

a,b,c=gets.split(/:/).map{|s|s.to_i}
puts b
a.times{puts b=(b*(1.0+c.to_f/100)).to_i}

次(79byte)。表示回数が一回多かったことに気づいた。また、1.0というように小数点以下の値を書く意味がないことに気づいた。

a,b,c=gets.split(/:/).map{|s|s.to_i}
a.times{puts b
b=(b*(1+c.to_f/100)).to_i}

次(69byte)。to_fの4byteがうぜぇ、と思ったので消した。これで端数切り捨てのために行っていたto_iも不要になってさらにサイズが減った。

a,b,c=gets.split(/:/).map{|s|s.to_i}
a.times{puts b
b=b*(100+c)/100}

ラスト(64byte)。ごちゃついてる3行目の記述を見直した。

a,b,c=gets.split(/:/).map{|s|s.to_i}
a.times{puts b
b+=b*c/100}

これでGolfコンペにidesaku (kaigi)としてエントリ、最終的に32/64位につけた。きわめて普通ってことだな。安心したものか、悲嘆に暮れたものか…。

プロゴルファーの皆様のレベルはもう半端ない。トップは49byte(!)なのだが、コードを読んでみると組み込み変数などを巧みに使ってサイズを落としている。うまいなぁ。

しかし、100の代わりに?dを使う方法は気づかなかった。?xは文字コードを表すリテラルで、dの文字コードがちょうど100なのだ。これで1byte削れる。るびまゴルフ第3回で説明してあるな。ちゃんと読んでおくべきだった。でも、putsをpにできたことに気づかなかったことのほうがくやしい!こんな簡単なことで3byteも節約できたのに!

これらを組み込めば、俺のコードは理想的にはこうなったわけだ。

a,b,c=gets.split(/:/).map{|s|s.to_i}
a.times{p b;b+=b*c/?d}

しかし、これでもまだ60byte。プロへの道は長く険しい。オレはようやくのぼりはじめたばかりだからな このはてしなく遠いGolf坂をよ…(未完)

しかし、単なる遊びかと思っていたが、意外とどうしてRubyの知らなかった顔が見えるようで新鮮で勉強になるものだなぁ。

6/21 追記

おかげさまでRubyKaigi2008 Golfコンペでは、パーより少し上くらいのスコアを取れ、賞品として Ruby クックブックをもらいました。まあ、現地の参加者よりも賞品の方が多かったので、参加者全員が賞品をもらえたんですけども。

http://dontstopmusic.no-ip.org/diary/20080620.html#p02

な、なんだってー!?がんばって前夜祭に出ておけばよかったorz