書いた人:浜地慎一郎
編集:うえだ
この連載ではゴルフについて扱います。ゴルフと言っても本当のゴルフではなく、コードを短く書くことを競うコードゴルフです。ゴルフについて詳しくは以下をご参照下さい。
簡単な問題を出題して、次回でその解答を解説しつつまた出題、というサイクルで進めて行きます。解けた人はこのページの下部にあるコメント欄でブログのURLを書いていただければ存在がアピールできるかもしれません。コードを縮めても特にいいことはありませんが、ちょっとしたパズルとして楽しんでいただけたら良いなと思います。
前回の問題は Unix の seq コマンドのようなものを実装する、というものでした。入力は
というようにカンマ区切りで標準入力から与えられ、これに対して
というように出力すれば良いという問題でした。また、おまけとして入力が空白で区切られている場合、オリジナルの seq と同様に ARGV から渡される場合、についても考えていただきました。以下では
と表記することにします。
今回の問題は、少し試してみると解答例として出題時に紹介した
のような upto や times を使う方法ではなかなか縮まないことに気付くのではないでしょうか。実際、コメントを頂いた皆さんはなんらかの形で Range クラスを使っておられました。
Range を使った最もわかりやすい形としては、uguisu_an さんの
や edvakf さんの
がありました。どちらも発想は近く、正規表現 /,/ でマッチさせて左側と右側をそれぞれ $` と $’ で取ってきています。ただし入力に改行が入っているために文字列のまま ‘3’..’7’ などとすることができず、 uguisu_an さんの方は to_i が二度入ってしまっているのが残念なところです。 (3) の方は $* が ARGV と同義であることがわかればすぐに理解できるかと思います。
edvakf さんはもう一つ、前置 * を使って .to_a を代用する工夫もしておられます。この演算は前回も紹介したのですが、今回は関数の引数を展開する意味で使われていて、厳密には .to_a とは違う動作になります。例えば puts *‘3’..’5’ であれば
ではなく
と同じ意味となっています。このケースではこの違いは特に関係ありませんが、後の例ではこの違いが効いてきます。
uguisu_an さんや edvakf さんの解答はゴルフとしてはまだ縮められるものの、日常的なワンライナーでも使えそうな感じで、実用性があって良いのではないかと思います。また、今回入力の末尾に改行があるかどうかを指定し忘れていたのですが、改行が無いとすると edvakf さんの (1) への解答は .chop を外すことができ、22Bytes とかなり短くなります。
grafi さんからは Range.new を用いた解答をいただいたのですが、これは完全に想定していなかった方法だったので面白いなぁと思いました。クラス名や new がゴルフで短いコードとして登場するのは極めて稀だからです。特に、ある程度慣れているゴルファーだとなかなか思いつけない方法なのではないかと思います。
さて、具体的には grafi さんのコードは
というものでした1。入力に入っているカンマをうまく使ってやり、例えば 3,5 というような入力に対して
というようなコードが eval されるようになっています。
(2)、(3) については、 Range.new を使う場合は eval を使わない方が短くなるのではないかと思います。
こういう意外な解法が気持ちよくハマるシーンがたまにあるのもゴルフの面白いところではないかと思います。
Range.new の解答にも登場していましたが、今回のメインテーマは eval を使いこなすことでした。 eval は通常のプログラムであればコードを読みにくくしてしまったりしますが、ゴルフでは時として強力な武器となります。特にポイントとなるのは、 eval が .to_i を兼ねることができることと、複数の値の .to_i を兼ねたり、入力の文字をそのまま流用できたりすることです。流用できない文字がある場合は、いかに短く eval できる形に変換できるかがポイントとなります。
まず (1)、(2) の解答を見ていきます。これらは入力に含まれるカンマや空白の処理がポイントになります。みしょさんは String#[] を右辺値として使う少し変わったテクニックでこれらを処理されていました。
また、maraigue さんは String#sub を使っておられました。
みしょさんはこの maraigue さんの解答を参考にさらに短いコードを発見されていました。
さらにこれは eval してる関係で String ではなく Fixnum の Range ができていることと、この * は先程少し説明した通り、 .to_a としてではなく、関数の引数を展開する意味で使われているため、 puts のかわりに p が使えて
とすることができます。 (1) に関してはこれが最短コードではないかと思っていました。
idesakuさんは split を使ってカンマや空白を処理されていました。
この方法だと三つの問題をほぼ同型で解けていて、わかりやすいです。このように入力を split で分割してから String#% でコード中の置きたい位置に %d などで配置する方法は汎用性が高く、様々な問題で時々有効な手法だったりします。
ただ、今回はこのような汎用性は必要なく、 String#join と同義である String#* を使うと短くすることができます。 String#* はみしょさんが maraigue さんのコードを参考にした (3) への解答として使用されていました。
これの puts を p に変えたものが (3) の最短だと思います。
この String#split と String#* を (2) に応用すると、 (2) は String#sub を使った 25Bytes より短くなり
という最短のコードができます。
こんなところかなぁと思いつつ、私のゴルフ場にも同じ問題を出題した後にこの原稿を書いていたのですが、プロゴルファーの eban さんが (1) に対して 23Bytes という記録を出してしまいました。そのコードは
というものでした。 (1) は入力にカンマがあるので今まではこれをうまく “..” に変換することを考えていたのですが、eban さんのコードはこのカンマをうまくそのまま生かすものでした。このように eval を使うコードでは入力を変換するよりもそのまま利用した方が良いケースが多いです。 Range.new の章で紹介した、 ‘Range.new ‘+gets を eval する手法もカンマをうまく生かしていると言えます。
なお A、B が大文字になっているのは a、b だと eval の作るスコープの外からアクセスできないため、定数にしてスコープを越えられるようにするためです。
今回は eval をメインテーマとして問題を考えてみました。 eval は普通のプログラミングで使用するとメンテナンスの難しいコードになってしまいがちですが、ゴルフでは強力な武器となります。今回ポイントとなったのは
というあたりかと思います。
eval を使うような問題の場合、自由度が高くなるために単純な問題でも最短がわかりにくくなりがちです。今回はさらに Range.new という意外なものが健闘したため、比較的簡単な問題を中心に出題しているるびまゴルフとしては珍しく、プロゴルファーも楽しめる問題となったようです。
一行の英単語が標準入力から与えられて、その末尾から 1Byte ずつ伸ばしていく形で文字数分の行を出力して下さい。具体的には
という入力に対して
と出力して下さい。入力は末尾に改行があるとしても無いとしても構いません。両方別々に解いていただいても良いですし、両方同時に解けるコードを考えていただくのも良いかと思います。入力の一行にはアルファベットや数値しか含まれないと仮定して良いことにします。例えば、入力末尾に改行が無いとすると
が解答例となります。パーは 35Bytes とします。
今回の問題は、テーマは考えてあったのですが具体的な問題を思いつかなかったため、にはさんに問題を考えてもらいました。ありがとうございます!
浜地慎一郎。ゴルフ場を経営しています。
解答をブログに書かれた方は、記事の URL と何か一言をコメントしていただけると嬉しいです。
※ コメントスパムが非常に多くなっているため、一定期間経過後にコメント受付を終了します。ご容赦下さい。
(3) の解は ARGV[0] から 3,5 という文字列が渡ってくる想定のようです。 ↩