るびまゴルフ 【第 4 回】

書いた人:浜地慎一郎

はじめに

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

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

前回の問題の解答と解説

前回の問題は、標準入力から一行ずつ受け取って行番号とその行の単語数・文字数・行の内容を空白で区切って出力するプログラム、例えば

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.

を出力するプログラムを書くというもので、解答例として示したコードは以下のようなものでした。

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

受け付け終了までに 3 人の方からトラックバックをいただきました。ありがとうございます。いずれの方もパーを越えておられましたが、以下ではじっくりとその解答を見てみましょう。

authorNari さんの解答

authorNari さんの 48Bytes の解

$<.map{|l|puts [$.,l.split.size,l.size-1,l]*' '}

というもので、入力行を順に受け取るのに $<.map という比較的短い表現を使っている、 $. という特殊変数には常に今まで入力された行数が入っている、というような特徴をうまく使っています。また、解答例の

"#{i} #{l.split.size} #{l.size-1} #{l}"

のように文字列リテラルに式を埋め込むより、

[i,l.split.size,l.size-1,l]*' '

のように配列を作って join してしまう方が良いと気付かれたのも素晴らしいと思います。 Ruby の Array#* は、 Array#join のより短い表現で、配列の要素を勝手に文字列化してくれ、ゴルフでよくあるカンマ区切りであるとか、空白区切りの出力が簡単に作れる、ということから、多用されます。

leonid さんの解答

さて、authorNari さんのコードは基本的な形としては理想的なのですが、種々のゴルフテクニックにより、縮める余地がかなりあります。

まず、入力行を順々に取ってくる方法としてよく使われる方法はだいたい3つあって、 authorNari さんの使われた

$<.map{|l|do_something(l)}

という方法、次に後置 while と Kernel#gets を用いた

do_something($_)while gets

という方法、そしてコマンドラインフラグを shebang に記述しておくと起動時のコマンドラインフラグをスクリプトから設定できる機能を用いた

#!ruby -n
do_something($_)

という方法です。

コマンドラインフラグ -n を指定すると、標準入力の各行に対して Ruby プログラムが呼び出され、また、それぞれの実行時に現在の行が $_ に入るようになります。今回の例では、コマンドラインフラグをうまく使うことによって大幅に縮めることができます。単純に ruby -n を使って authorNari さんのコードを書き換えてみると、

#!ruby -n
puts [$.,$_.split.size,$_.size-1,$_]*' '

50Bytes 。むしろ長くなってしまいました。このコードでは入力にコマンドラインフラグ -n を使って、出力に puts を使っているのですが、入出力を同時に行なうフラグ、 -p を使うと少し短くなります。 -p を指定すると、 -n の効果に加えて、各行の実行後に $_ を出力してくれるようになります。

#!ruby -p
$_=[$.,$_.split.size,$_.size-1,$_]*' '

これで 48Bytes 。元の状態に戻りました。このコードの中で長い部分を見てみると、 .split.size が非常に長くて気になります。入力の各行を split する、というのは非常によくあるスクリプト言語のユースケースであるため、実はこれをサポートするコマンドラインフラグ、 -a があります。 -a をつけると、入力行が split され、 $F に入った状態でプログラムが実行されます。これを使うと、

#!ruby -ap
$_=[$.,$F.size,$_.size-1,$_]*' '

43Bytes 。大幅に縮みました。leonid さんの 42Bytes の解は、このコードに一工夫したものでした。

#!ruby -apl
$_=[$.,$F.size,$_.size,$_]*' '

コマンドラインフラグに -l をつけると、 $_ に改行が入らず、かわりに print をする時に改行が1つ入るような挙動をします(特殊変数 $\ を変更することによってこれを実現しています)。これにより、 $_.size-1 の -1 の部分をなくすことができ、 1Byte 縮めることができます。

weda さんの解答

さて、今回はプロゴルファーでない方で、想定解に到達された方がいました。素晴らしい。weda さんの 38Bytes 解で、以下のようなコードです。

#!ruby -ap
$_=[$.,$F.size,~/$/,$_]*" "

split の次に目につく場所である、 $_.size-1 をうまく短く表現しています。これは、 String#=~ や Regexp#=~ がマッチした位置を整数でかえしており、さらに Regexp#~ (前置オペレータであることに注意) が self =~ $_ と等価であることをうまく利用しています。 /$/ にマッチさせれば、改行をのぞいた終端にマッチするため、 -1 も不要になります。

別解

実は今回は、想定解は2つありました。以下のようなものです。

#!ruby -ap
$><<[$.,$F.size,~/$/,p]*" "

$><< は暗号的ですが、 $> は STDOUT であり、それに << で文字列を流し込んでいて、つまり print の少し短い表現です。コードの中で "2 3 11 " というような数値3つを出力しておいて、元の文章の出力は -p に任せていることになります。

元の文章の直前に空白が必要なのですが、このコードでは p を配列の末尾につけることによって、これを実現しています。 Kernel#p はご存知の通りデバッグ用の pretty printer なのですが、引数無しで呼び出すと何もせず、さらに nil をかえします。これによって、 1Byte で nil を得ることができます。

今回は文字列を1行ずつ読み込んで何か処理をして出力する、というゴルフだけでなく日常的にもよくあるシチュエーションでコードを短くするテクニックをまとめて紹介してみました。アルゴリズムが同じであれば、こういった小細工の勝負になるため、今回紹介したような、コマンドラインフラグ、種々の特殊変数、 String#* 、などは把握しておきたいところです。

今回の問題

前回は比較的複雑な問題だったので、反省して短い問題にすることにしました。

整数 n に対して、 0 ならば 1 、そうでないなら 0 をかえすような表現が欲しいことは多々あります。 Perl や C などでは、 !n とすれば簡単に得られますが、 Ruby ではあまり簡単ではありません。まず思いつくのは、三項演算子 ?: を用いた、

n==0?1:0

というようなコードでは無いでしょうか。この表現を縮めてみて下さい。また、少し改変した、

1+(n==0?1:0)

というコードも同様に縮めてみて下さい。必要なら、 n が負でないことを仮定したり、 n を破壊するコードも OK としますが、なるべく使わない方法も考えてみて下さい。パーは後者で 9Bytes です。

著者について

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

リンク

トラックバックスパムが多いため、トラックバックは廃止しました。

解答をブログに書かれた方は、記事の URL を投稿してください。

(comment plugin is disabled).
Last modified:2008/10/04 10:03:29
Keyword(s):
References:[Rubyist Magazine 0024 号] [0024 号 巻頭言] [るびまゴルフ 【第 5 回】] [各号目次]