るびまゴルフ 【第 6 回】

書いた人:浜地慎一郎

はじめに

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

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

前回の問題の解答と解説

前回の問題は、標準入力から複数の行を受け取って

  • 1 問目:同じ内容の行を削除しつつ出力する(パー:30Bytes)
  • 2 問目:同じ内容の単語を削除しつつ出力する(パー:45Bytes)

という 2 問でした。どちらもどうやって同じ内容のものを削除するか、どうやって入力を得るか、が肝となっています。

uniq や readlines を使った解答

このような重複を取り除く処理をする場合、Array#uniq を使うのがまず思いあたるのでは無いでしょうか。

Sixeight さんの解答Maraigue さんの解答、ujihisa さんの解答1 では、実際に uniq を使った解答をいただきました。

Sixeight さんや ujihisa さんの解答は、1 問目が

p $<.readlines.uniq  # 19Bytes(puts を使うと 22Bytes)

で、2 問目が

p $<.readlines.map(&:split).flatten.uniq  # 40Bytes(puts を使うと 43Bytes)

というものでした。また Maraigue さんの解答は 1 問目が

puts$<.read.split($/).uniq  # 26Bytes

で、2 問目が

puts$<.read.split.uniq  # 22Bytes

というものでした。Sixeight さんの解答は IO#readlines を使っておられるため 1 問目 の方が短く、Maraigue さんの解答は $<.read.split とすれば単語への分割は簡単であるため 2 問目の方が短くなっています。

また、メールにて U さんから頂いた 1 問目の解答は

puts readlines.uniq  # 19Bytes

と Kernel#readlines を利用する形になっていて $<. が無い分、この方が短くなっています。Kernel にはいくつかの関数が組み込み関数として用意されており、可能ならそれらを使用することでより短くすることができると思います。

このあたりがパーを切る中で想定していた解答、言ってみればバーディあたりの解答でした。

uniq と readlines を短くする

星一さんの解答では、まず外部コマンド sort を使った 1 問目への解答が示されていました。

puts`sort -u`
これはまぁ、面白いのですが外部コマンドを使うのは少々ずるい解答ではあります。星一さんは Array#uniq を短くする方法も同時に発見しておられました。Array# という Array を集合とみなして和演算をする方法がそれで、和演算をする時に重複除去を行なってくれるため、短くすることができます。星一さんの解答は、1 問目が
puts$<.map|[]  # 13Bytes

で、2 問目は

puts$<.read.split|[]  # 20Bytes

というものでした。前者では、readlines よりも $<.map の方がさらに短いという点を利用されていました。$< は IO から継承されているクラス、つまり入力行に対する Enumerable ですので、map を呼んでやれば readlines を短く表現することが可能になっています。

$<.map を短くする

1 問目に関して、$<.map はさらに短くすることが可能です。$<.map を使っているのは要は Enumerable#to_a を使いたいというのが本当のところで、to_a を発動できる最小のメソッドが map であるということです。あまり知られてないことですが、Ruby では splat 演算 (前置 * ) を行なう時に to_a が暗黙のうちに呼ばれるという機能があって、これを使うことで短くできます。

ujihisa さんはこれを生かした表現を考えておられ、その解答は

$><<(a=*$<).uniq  # 16Bytes

というものでした。このままでは $<.map より長くなってしまっているのですが、代入時以外にも splat 演算が起きるタイミングがあり、それを使うとさらに短くすることができます。配列リテラル内で前置 * をつけてやると良い、というのがこれで、これを利用すると、$<.map は [*$<] と表現することが可能です。この方法を単純に使うと

puts [*$<]|[]  # 13Bytes

となり、パーサの都合で puts の後に空白が必要となってしまうのですが、組み込み変数 $* が空配列であることを利用してやるとうまく空白を消すことができます。

akai さんの解答では実際にこのテクニックが用いられていて

puts$*|[*$<]  # 12Bytes

と、星一さんの解答よりも 1Byte 縮めておられました。この解答が1問目の最小コードでは無いかと思います。実際、この1問目は私のゴルフ場でも過去に出題されていた問題で、そこでも 12Bytes が最小となっていました。


星一さんはあと一歩でしたが、akai さんの解答はこちらの想定解どおりでした。

組み込み変数 $_

2 問目に対する akai さんの解答は星一さんのものとほぼ同じなのですが、$<.read を gets(p) を使った

puts$*|gets(p).split  # 20Bytes

というようなものでした。$<.read と gets(p) は同じサイズですので、こちらは同着となっています。Kernel#gets は引数が無いと一行入力する関数なのですが、nil を引数として渡してやると入力を全て読み込む関数になり、$<.read とほぼ同じ挙動をすることになります。p は無引数ですと何も出力せずに nil を返すため、1Byte で nil を表現することができます。

実はこの 2 問目はさらに 1Byte 減らすことができます。先程「gets(p) は $<.read とほぼ同じである」と書いたのですが、「ほぼ」というのは実際は少し挙動が違うということで、gets(p) は組み込み変数 $_ への代入も同時に行なうという点が違っています。

Ruby 1.8 では組み込み変数 $_ に作用する関数がいくつか Kernel 内に定義されていて、例えば Kernel#split は $_.split と同じ意味となります。この点を利用してやると gets に対して () が不要になり

gets p;puts$*|split  # 19Bytes

というように 1Byte 短くすることができます。これが 2 問目に対する最短のコードではないかと思います。Ruby 1.9 では Kernel#split が存在しないため、残念ながらこのテクニックは使えません。

この問題は私のゴルフ場でも出題してみたのですが、そこではこれとほぼ同じ形の 19Bytes の解答をゴルファーな方々からいただきました。


星一さんと akai さんはほんの少し足りていないものの、ほぼ想定解に近い解答でした。ありがとうございます。

今回のまとめ

今回は Array# 、Kernel#gets、 Kernel#split、組み込み変数 $* が肝となる問題でした。これらは普通に Ruby を使う上ではあまり使わないものばかりなのですが、7 人がほぼ同様の解答をしたことからわかる通り、ゴルフ界隈では割と常識的なものだったりします。あと数 Bytes の差で追いつけないというような場合、この手のテクニックに気付いていないケースがあるのではないかと思います。

今回の問題

seq という Unix コマンドがあります。このコマンドを使うと、数値を2つ引数にして、その間にある数を出力することができます。例えば 3 と 5 を引数として実行すると

% seq 3 5
3
4
5

というような実行結果になります。

今回はこのコマンドを実装してみて下さい。入力はコンマで区切られて標準入力から与えられるものとします。例えば

3,5

という入力に対して

3
4
5

と出力するようにして下さい。例えば

a,b=gets.split(',').map{|v|v.to_i}
a.upto(b){|i|puts i}

というようなコードと同じ出力が得られるようにして下さい。パーは 50Bytes とします。

また余力があれば、入力が

3 5

と空白区切りで与えられる場合、 seq のように引数として与えられる場合、つまり ARGV を使う場合も考えてみて下さい。

今回の問題もある程度の期間をおいてから、私のゴルフ場で出題してみようかと思います。

著者について

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

バックナンバー

解答コメント

解答をブログに書かれた方は、記事の URL と何か一言をコメントしていただけると嬉しいです。



  1. 使っておられたブログサービスの終了につきリンクはありませんでした