るびまゴルフ 【第 3 回】

書いた人:浜地慎一郎

はじめに

この連載ではゴルフについて扱います。ゴルフと言っても本当のゴルフではなく、コードを短く書くことを競うコードゴルフです。ゴルフについて詳しくは以下をご参照下さい。

簡単な問題を出題して、次回でその解答を解説しつつまた出題、というサイクルで進めて行きます。解けた人はトラックバックなどしていただければ存在がアピールできるかもしれません。コードを縮めても特にいいことはありませんが、ちょっとしたパズルとして楽しんでいただけたら良いなと思います。

前回の問題の解答と解説

前回の問題

-2000.step(-10000,-10) do |v|
  puts v
end

というプログラムを縮めろ、という問題でした。まずは do / end をブレースを使って書き換える、整数の表示であれば Kernel#puts の代わりに Kernel#p が使える、などを使うと、

-2000.step(-10000,-10){|v|p v}

と 30Bytes になります。ここからが本番です。

共通部分のくくり出し

ゴルフでも、通常のプログラミング同様、共通部分のくくり出しが重要です。 - が三度出てきますので、出力部分を p -v に変えて 2Bytes 削れます。

2000.step(10000,10){|v|p -v}

これで 28Bytes。0 がやけに多いのも冗長です。数字を全て 10 で割って最後に 10 を掛けてみましょう。

200.step(1000,1){|v|p -v*10}

この変更では縮んでくれませんでした。しかし step の第二引数が 1 なので、step を upto または times にすれば縮められます。upto と times はどちらが良いかは問題に強く依存しますので両方試してみた方が良いでしょう。

200.upto(1000){|v|p -v*10}
801.times{|v|p -2000-v*10}

両方同じサイズで 26Bytes ですね。これで、前回設定したパーを切ることができます。ここまでやっていただいた方は頑張って下さってありがたいなぁというところです。しかし upto のバージョンはここから更に数値リテラルを使った三つの小細工が適用できます。

数値リテラル特集

1000 は浮動小数で記述すると 1e3 にすることができて 1Byte 削ることができます。このテクニックは C や Perl ではよく使えるのですが、Ruby では浮動小数を puts や p すると .0 が出力についてしまうため、実のところあまり利用シーンがなかったりします。

200.upto(1e3){|v|p -v*10}

次は 200 というリテラルも縮めることができます。 2Bytes で表現できる数値リテラルは常識で考えれば 99 が最大なのですが、文字コードをあらわす数値リテラル ?x (x は任意の文字) を用いると、例えば ?~ で 126 を表現できます。この考えを延長して、 ?\xc8 (0xc8 == 200) をバイナリで入力してやれば、 200 を 2Bytes で表現できます

?\xc8.upto(1e3){|v|p -v*10}

注:このテクニックは Ruby 1.8 までしか使えません。1.9 では ?x が文字列リテラルに変更されたからです。

最後に、10 もまだ縮みます。正確には -10 をもっと短く表現する方法があり、数値の補数演算子 ~ を使ってやると、 -10 == ~9 であることを利用して縮めることができます。

?\xc8.upto(1e3){|v|p ~9*v}

p-10v は p.-(10v) と解釈されるため、空白を入れて、 p(-10*v) であることを明示してやる必要がありました。しかし、 ~ は - と違い二項演算子として使用できないため、 p の後の空白を省略できます。

?\xc8.upto(1e3){|v|p~9*v}
この 22Bytes が想定解 (Ruby 1.9 なら 200.upto(1e3){ v p~9*v} の 23Bytes) となります。

運まかせ

最後に、運まかせな手法によってさらに縮めることもできます。運まかせで問題を解く際によく利用されるのは、rand のような長い関数ではなく、組み込み変数 $$ と Kernel#id です。

$$ は現在のプロセス ID (PID) を持つ組み込み変数で、手元の GNU/Linux 環境では 300 以上 32768 以下の数値を取り得るようでした。なんでもいいからそれなりに大きな数値が欲しい時によく利用されます。組み込み変数は普段のプログラミングにはあまり使われないものの、ゴルフには重宝されるものが他にも多くそろっています。

id は object_id の別名で obsolete になってはいるようですが、問題なく使用できます。id を実行すると、トップレベルオブジェクトの ID、つまるところ適当なメモリの番地が返ってきます。Ruby の実装の都合上必ず 4 の倍数で、ポインタのアドレスですので相当に大きい数になることが普通です。

今回の場合、1e3 を $$ に変えて PID がたまたま 1000 になるタイミングで実行すれば 21Bytes となります。

今回の問題

標準入力から一行ずつ受け取って、行番号とその行の単語数、文字数、行の内容を空白で区切って出力するプログラムを書いて下さい。具体的には、

TEST
foo bar baz
The quick brown fox jumps over the lazy dog.

を標準入力から受け取って (最終行を含め、入力の行末には改行があるものとします)、

1 1 4 TEST
2 3 11 foo bar baz
3 9 44 The quick brown fox jumps over the lazy dog.

を出力するようなプログラムとして下さい。下記のコードと同じ動作をすれば OK です。

i=0
while l=STDIN.gets
  i+=1
  puts "#{i} #{l.split.size} #{l.size-1} #{l}"
end

今回はゴルファーには常識的なのですが、普通の人にはなかなか厳しい問題かもしれません。パーは 50Bytes 程度でしょうか。

著者について

浜地慎一郎。ゴルフ場を経営しています。

バックナンバー

トラックバック