いろは歌。
in Ruby.
!@THEqQUICKbBROWNfFXjJMPSvVLAZYDGgkyz&[%r{\"}mosx,4>6]|?'while(putc 3_0-~$.+=9/2^5;)<18*7and:`#
ということで、ASCII の制御文字じゃない部分、' ' (0x20) から '~' (0x7E) まで95文字をちょうど一回ずつ使って、 その95文字をちょうど一回ずつ標準出力に書くプログラムです。Ruby会議に合わせて開かれた TRICK 2013 という、プログラムの"お前はなにをやっているんだ度"を競うコンテストに出してみたら優勝してしまいました。 やった!
「各文字を1回しか使わない」 という制約が真っ先に効いてくるのは、 これすなわち、 1個の変数を1度しか使えない、 というところです。 C言語のfor文などに見えるように、 変数というのは宣言/初期化して更新して検査して値を使う、 と4回くらいコード中に現れるのが普通です。 もっと高級なパラダイムやライブラリに頼れば2回にはなりますが、 1回というのは、流石に、その、変数という概念の存在意義を問われる事態です。 ちなみに Ruby での "高級" なやりかただと、 色々なループ処理を Enumerable や Integer へのブロック渡しで処理するものですが、
32.upto(125){|c| putc c}
いきなり | を2回使われてしまって泣きそうです。というわけで、
$.
という、最初から整数が入っていて書き換えも可能な便利なグローバル変数を使えば初期化不要。$.+=1
は普通なんですが、
これを更新だけで終わらせず先に繋げる必要があります。
上のコードでは、$.
の初期値は0なので30ちょい足して ' ' にしてあげています。
代入式という優先度最低の演算子を括弧なしで他の演算子の右手側にもってこれるのは
Ruby の特徴的な部分だと思います。
つまり、a+b=c
が (a+b)=c
と解釈されてエラーにならず
a+(b=c)
になるんですよね。こんなところで貴重な括弧を温存できるのはありがたい。putc
で、変数を表示に使うわけですが、
この「いろは歌」以外に何の用途があるのかわからないんですが putc は引数をそのまま返すので、
さらに繋げて条件判定に値を引き回せます。
というあたりで、「各文字を1回しか使わない」 を解決しました。
細かい所ですが
+=1
と 31+
で2回 +
の文字を使うのを避けるために -~
というマイナーな二項演算子を使っていまして、
x-~y
は x+y+1
と同じ意味です。
嘘です。 ビット反転 ~
してからの引き算ですね。
実は大変なのは、「他の全ての文字を1回使う」 という義務の方です。 まあ全部コメントか文字列リテラルに突っ込めば全部使えるのですが、 それは面白くないので、できるだけ面白く文字を捨てるように心がけました。
\"
, ?'
, :`
。
「普通は2個対になって使う」文字の処理にまず注意が必要で、
引用符は普通に使うと同じ文字で閉じないといけないのでアウトです。
引用符になってしまう文字は Ruby に3つありますが、 幸い、 殺し方も3通りあります。
#
は一番最後に回して自分以外をコメントアウトしないようにしました。4>6
...3_0-~$.+=9/2^5;)<18*7
意外と面倒なのは演算子の処理で、+-*/<>=~^|&!
をすべて処理するには、
その個数分の operand を与えてやらないといけないわけです。
さっき述べたように変数はあんまり使えないので、
オペランドはリテラルから作ってやりたくて、 1
と書かずに 9/2^5
と書くと、 演算子消費義務を二つも果たせてめちゃくちゃ嬉しい、 などなど。
細かい所で、bool っぽい文脈に文字列リテラルを置くと
warning: string literal in condition
という警告が出て格好悪いので、
頑張って避けました。
3_0
のように数値リテラル中の _
とか、
7and
の間にはスペースなしでもいいんだ、 とか
(...;)<18*7
気をつけたのは、そんなところです。
賞を頂いておきながらなんですが、実は、自分としては、このコード、まだまだ不満です。 なぜって
!@THEqQUICKbBROWNfFXjJMPSvVLAZYDGgkyz&[%r{\"}mosx,4>6]|?'while(putc 3_0-~$.+=9/2^5;)<18*7and:`#
プログラムとして 意味のある 部分が全体の3分の1しかない。 他は頑張って意味のないことをさせて、 文字をひたすら捨てています。 95文字すべてとはいいませんが、 できるだけ多くの文字がプログラムの働きに貢献する、 そんな 「Ruby いろは歌」 はないものでしょうか。 ぜひ、これを見た皆様に挑戦していただきたいと思っています。
自分でも挑戦したのですが、うまく行きませんでした。
実のところ、最初の予定では、まったく文字が重ならない二つのプログラムを二つ並べて、
片方は stdout に、 片方は stderr に 95 文字を吐くものを書く予定でした。
…そのくらいやれば60文字くらい使うだろうし、 $. を使うトリックはすぐにできたので、
もう一個書くくらいは簡単だと思っていたのです。 …が、while putc
を避けてもう一つ同じ処理を書くのは……少なくとも締め切り当日の1日では無理でした。
無念。
たぶん、逆方向からアプローチした方が面白いんではないかと思っています。 つまり、Ruby の標準ライブラリと言語仕様をサーチして、 できるだけ英大文字と小文字を消費できるメソッドの組み合わせを探索する。 そこで得られたメソッドの組み合わせから、落語の三題噺のように、 面白い動作をするプログラムを編み出す。 機会があればやってみたいと思います。
というわけで最後にもう一度、 できるだけ多くの文字がプログラムの働きに貢献する、 そんな 「Ruby いろは歌」 はないものでしょうか。 ぜひ、これを見た皆様に挑戦していただきたいと思っています。