るびまゴルフ 【最終回】

編集中

書いた人:浜地慎一郎
編集:うえだ

はじめに

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

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

前回の問題の解答と解説

前回の問題

hoge

に対して

e
ge
oge
hoge

を出力する、という問題でした。

文字列のインデックスを指定する方法

thinca さんMaraigue さんは解答例をゴルフして、文字列のインデックスを使った方法でパーを越えておられました。

thinca さんと Maraigue さんのコードは

1.upto(gets.size){|i|puts$_[-i,i]}  # (34B)

というようなものでした。二人ともこのコードは末尾に改行が無いことを前提にしていることに気付いておられました。さらに、Maraigue さんには改行がある場合の解答も提示していただきました。

2.upto(gets.size){|i|puts$_[-i,i]}  # (34B)

正規表現を使う方法

ゴルフでは、素直に配列や文字列のインデックスを使うと長くなってしまうケースが多いようです。今回もまさにそういった問題で、文字列のインデックスを離れて正規表現を使うと劇的に短くすることができます。

前回記事のコメント欄 に書いていただいた kaz さんの

gets;puts$&while~/.#$&$/  # (24B)

というコードと、それを wataken44 さんが縮めた

gets;puts$&while/.#$&$/  # (23B)

というコードが正規表現を用いた解答でしたが、今回の想定解はまさにこの 23B のコードでした。最初の gets が標準入力を一行読み込んで $_ に代入しているところは説明不要だと思いますが、while 文の方を少し細かく見ていきましょう。

まず #$& は #{$&} の省略系で、Ruby では何故か (おそらく Perl の影響で、現在は黒歴史になりつつあるものの一つという感じだろうと思いますが) #$_、#@member、#@@static などの表記を用いると文字列リテラルの中で変数を参照することができます。

$& は前回マッチした文字列が入っている特殊変数です。初期値は nil ですが nil を文字列化すると空文字列になりますので、初回に実行する正規表現は /.$/ となり、末尾の一文字 (入力が hoge であるとすると e) にマッチすることになります。そして、while の中ではこのマッチした末尾一文字の文字列を puts で出力します。

次に条件式を評価するときには $& には末尾一文字が入っているため、入力が hoge だと /.e$/ という文字列になります。これによって末尾二文字をマッチさせることになり、条件式の評価を繰り返すことで文字列を末尾から一文字ずつ足していくことができます。

今回は $& がうまく使えましたが、正規表現の副作用でセットされる特殊変数 ($'、$`、$+、$1、$2、……) は有用なものが多いので把握しておくと良いと思います。特殊変数を覚えるのは大変ですが、このあたりのものは日常的なワンライナーでも便利に使用できたりするので、意外と覚えておいて損は無いのではないかと思います。余談ですが、Perl の方がゴルフ的に覚えないといけない特殊変数が多くて大変だったりします。

なお wataken44 さんが気付かれた ~ が省略可能であるという点は、条件式に正規表現が出てくるとその正規表現を実行してマッチしたかどうかによって分岐する機能を使ったものです。これは Ruby 的には気持ち悪く、正規表現リテラルを条件式に置くと warning が出たりもするのですが、ゴルフでは時々出現する手法だったりします。

今回のまとめ

今回は、「文字列を一文字ずつ末尾から取っていく」という処理が正規表現によって短くなるという例を紹介しました。正規表現は使えることに気づくかどうかが勝負の分かれ目になるため、ゴルファーは文字列が絡む問題を見るとすぐに「これは正規表現を使えないだろうか……」と考えるように思います。

正規表現は通常のプログラミングでも重要ですが、ゴルフではその記述力や特殊変数への副作用の多さから特に重要です。複雑な問題で正規表現を使っていると毎回新しい発見があり、その奥の深さを実感します。反面、なかなか体系立てて正規表現の利用法を解説するのは難しいのではないかと思うのですが、今回の例でその力の一端をご紹介できたのではないかと思います。

正規表現の力をもっとふんだんに使っている例としては、括弧の対応が取れているかどうかを調べる問題 (の Ruby による解答)が見当たりました。興味があれば、実際にどう動いてるか調べてみるのも面白いんじゃないかと思います。

おしらせ - 最終回にあたって

突然ですが、るびまゴルフの連載は今回で最終回となります…… というわけで今回の出題はありません。

もしまだゴルフを続けたい!という方がいましたら、ぜひ codegolf.com私のゴルフ場 で続きを楽しんでみて下さい。前者は Ruby、Perl、Python、PHP の 4 言語でゴルフを楽しめるサイトで、ネタバレなどが無く、個人のランキングなどが出るので真剣な勝負が楽しめます。後者は私が運営している 71 言語 ( 2009 年 11 月現在) でゴルフが楽しめるサイトで、最近の問題は大抵 2 週間ほど勝負した後でコードが公開される仕様になっています。公開されたコードを解析してテクニックを盗んだり、なんとなくヘンな言語でのゴルフに挑戦したり、問題を投稿してみたり、色んな楽しみ方ができると思います。

さて、この連載を一通り読んだら上記のサイトで好成績が収められるかというと、実はなかなか厳しいのではないかなと思っています。この点について、るびまゴルフを始めた動機と絡めて、少し書かせてもらいたいと思います。

ゴルフの基本テクニックと論理的思考や発想の転換

ゴルフの話をしていて私がよく聞くのは、「ゴルフはちょっと挑戦してみたけど基本的なテクニックがわからなくてお話にならなかった」というようなものです。これはこれで素直な感想なのでしょうが、私は「ちょっと違うぞ」と思うのです。確かに基本的なテクニックで差がついている部分も大きいと思うのですが、本当に我々が努力して、そして楽しんでいる部分は、基本的なテクニックを抑えた上での膨大な時間を投下して行なう 論理的思考や発想の転換の勝負 なのです。

とは言え、そういった部分を伝えるのはなかなか難しいようで、普通に書いたコードを空白を削るなどして少し縮めてはみたものの、後で我々の書く異様な見てくれのコードと比べて、「ああ、やっぱり何か俺の知らないテクニックがあるんだな」とだけ思ってしまう人が多いように思います。本当に違う部分は往々にしてアルゴリズムの部分だったりするので、そういった定石レベルの差異だと思われてしまうのはゴルファーとしてはちょっと残念なことだったりします。

発想の転換こそが重要だった例

そういった発想レベルの差がとても判りやすく出た例を一つ紹介したいと思います。私のゴルフ場に 入力を受け取って、入力の奇数行目だけを出力する という問題が出題され、5 人のゴルファーが 18B のコードを定石的な手法で書きました。こういうコードです。

#!ruby -p
$_*=$.&1

ruby -p で各行を読み込んで $_ に代入し、プログラムを実行した後で $_ を出力するフラグです。$. には読み込んだ行数が入っているので、$.&1 が 1、0、1、0、…… と繰り返されることを利用し、偶数行では $_ の中身が空になるようにしています。連載で紹介したゴルフの定石的な作法の組み合わせ、という感じのコードです。

ところが、トップの kurimura さんはただ一人、全く違う発想で 17B を出しました。こういうコードでした。

#!ruby -p
$<.any?

ruby -p によって全ての行が読まれるため、偶数行を読み捨てればそのまま奇数行だけが出力される、という発想です。この発想自体はそれほど意外なものではないのですが、Kernel#gets や IO#gets などを使うと (奇数行も入っている) $_ を破壊してしまうため、その方向性は難しいと考えていました。IO が Enumerable であることを利用して Enumerable#any? という意外なもので一行読み捨てを実現したこのコードには、当時多大な感銘を受けた記憶があります。

このコードは、見た目は 18B のものよりシンプルなのに短く、ゴルフの定石的なテクニックよりアイデアの差が出ている大変良い例となりました。もう少し長い問題だとこういうアイデア勝負的なことはよく起きるのですが、このような短い問題でゴルフの定石以外の部分で勝負がつくのはとても珍しいことでした。

るびまゴルフを始めた動機

というわけで、るびまゴルフの執筆を始めた動機は色々あるのですが、一番大きいものは「とりあえずこれを読んでおけば基本的なイディオムの 8 割程度はカバーできる」というようなものがあるといいな、と思ったことです。そういうものがあれば、それを一通り読んだ人は定石の部分ではなく、本質的な部分の勝負を手っ取り早く楽しめるかなぁと。

8 割というカバレージを実現できたかは分かりませんが、本当によく使う部分についてはこの 8 回の連載でだいたい説明できたように思います。うまく説明できたという自信はさっぱりありませんが、基本テクニックを例と並べて列挙する、ということは少なくともできました。

今回を最終回にしようと思ったのも、そろそろ基本テクニックは一通り紹介し終えたかなぁ、と思ったからです。定石的な部分を越えた発想の違い的な部分を紹介するのは、変態列伝的な内容にすれば話としては面白いとは思うのですが、各問題ごとに驚くほど違うので他の問題に全く適用できず、ゴルフ的な実用性は乏しかったりします。例えば、先程紹介した any? は (私は) 他の問題で一切使ってないんじゃないかと思います。

そんなこんなで、だいたい当初の目標は私なりに達成できたんじゃないかなぁと思います。良かったです。今までサポートしていただいたり、そもそも発行媒体を維持してこられた るびま 編集者の方々、特に初期の頃に突然の思い付きに対して相談に乗っていただいた ささだ さんと、途中から編集を担当していただいた うえだ さんに感謝したいと思います。また、今まで読んでいただいた方々、特に問題を実際に解いてみてくださった方々にはとても感謝しています。おかげでゴルフを普段していない人がどういう思考をするのかが想像でき、ずいぶんと書きやすくなったということもありますし、ずいぶんと新しい発見もありました。

著者について

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

Last modified:2009/11/26 04:11:27
Keyword(s):
References:[Rubyist Magazine 0028 号] [0028 号 巻頭言] [Rubyist Magazine 六周年] [各号目次] [prep-0028]