読者です 読者をやめる 読者になる 読者になる

重複なしで Hello, world! を Ruby だけで

CodeIQ に Hello, world! を3つ以上の言語で書きなさい、ただしそれぞれで使われてる文字種に重複があったらダメ、という問題がありました。

http://nabetani.hatenablog.com/entry/codeiq_hwx3_q766

私の解答は6言語使ったものでした。

http://shinh.hatenablog.com/entries/2014/03/18

今回は Ruby だけを使って、4つの Hello, world! ができました。言語が一つしか無いと複数言語を使うより色々なことをする余地が減るので、当然大変なので6つとかはちょっと無理です。

5つも厳しいかなぁと思っていて、なんらかの文字を出力する方法が4つ以上思いあたらないからです。4つというのは

  • puts/putc/print 系
  • IO#<<
  • eval
  • ラムダを使った eval

の4つです。あとは単純に任意文字列を作る手段が足りなくなっていきます。順に見ていきます

ひとつめ puts+putc 使用文字種10種類: " +89;cpstu"

putc 9+9+9+9+9+9+9+9;putc 8+8+8+8+8+8+8+9+9+9+9+9;putc 9+99;putc 9+99;putc 8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+9+9+9+9;putc 8+8+8+8;putc 8+8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+8+98;putc 9+99;putc 8+8+8+8+8+8+8+8+9+9+9+9;putc 8+8+8+9;puts

色々試しましたが、最終的に丸括弧を消費すると最後で詰むので、丸括弧を使わない、つまり .chr などを必要としない putc がありがたいと判断しました。 8 と 9 だけを使っているのは、後で8進数を使うのでこいつらだけは余るのです。 10 は 8 と 9 では作れませんが、そこは puts で。

ふたつめ IO#<< 使用文字種12種類: "\n$/<=DOSTUz~"

STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
...

このコードは長いので以降省略。やってることは単純に、長い文字列作って、その長さを /$/=~ で調べて、それが文字コードになってるから文字列に << してその後 STDOUT に << する、と。

二つの << の優先順位が同じなので、なんとかするために = を使ってます。まぁ正規表現の =~ にも使ってるから丁度良い。文字列は < なのですが、 > が必要なので STDOUT を。まあ大文字はどうせ余りまくります。 $ も重要だと思ってて、このコードで使う必要は別に無いんですけど、最終的にいらなかったのでそのまんま。

みっつめ eval 使用文字種16種類: "\t%01234567Q\\aelv"

eval	%Q%\160\165\164\163\42\110\145\154\154\157\54\40\167\157\162\154\144\41\12\42%

なんというか、そのまんまです。数字をケチってきたのでラクできます。

よっつめ lambda 使用文字種26種類: "!\"#&()*,-.:>?VW[]^_doqrw{}"

->(__){->(&_){_["",:"#{__[?q.ord^?>.ord]}r#{__[?d.ord^?,.ord]}#{__[?d.ord^?).ord]}#{__[?r.ord^?!.ord]}#{__[?d.ord^?!.ord]}","#{__[?q.ord^?V.ord]}#{__[??.ord^?{.ord]}#{__[?q.ord^?:.ord]}#{__[?q.ord^?:.ord]}o,#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}#{__[?{.ord^?-.ord]}or#{__[?q.ord^?:.ord]}d!#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}",?w.ord^?W.ord,?w.ord^?}.ord]}[&:"#{__[?q.ord^?#.ord]}#{__[??.ord^?{.ord]}#{__[?d.ord^?).ord]}d"]}[[*?!..?}]]

知らないと意味不明ですが、 Ruby で記号だけで Turing complete にするための、 ->(&_){_["",:"eval","code..."]}[&:"send"] というイディオムを使ってます。ここに ->()[]{}#: などを残すためにこれまでで苦労してました。

追記。 () 残す必要は無かったでした。

->__{->&_{_["",:"#{__[?q.ord^?>.ord]}r#{__[?d.ord^?,.ord]}#{__[?d.ord^?).ord]}#{__[?r.ord^?!.ord]}#{__[?d.ord^?!.ord]}","#{__[?q.ord^?V.ord]}#{__[??.ord^?{.ord]}#{__[?q.ord^?:.ord]}#{__[?q.ord^?:.ord]}o,#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}#{__[?{.ord^?-.ord]}or#{__[?q.ord^?:.ord]}d!#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}",?w.ord^?W.ord,?w.ord^?}.ord]}[&:"#{__[?q.ord^?#.ord]}#{__[??.ord^?{.ord]}#{__[?d.ord^?).ord]}d"]}[[*?!..?}]]

感想とか

どこに何を使うか、割と苦労したんですが、トリッキーなコードを書くとどうしても文字種消費しがちなため、完成してしまうと割とシンプルな4つになりました。難易度的にも言語バラバラ6種の方がやや大変でした。

5つ目は文字を出力する手段さえあれば、かなりキツいでしょうがなんとかなるかもしれません。ずるいですが `` を活用するとかも考えましたが、 1>&2 を使わずに出力できる気がせず。

Perl で同じことをすると、拡張正規表現での eval が使える少し古い Perl でも 3 種類が限度なんじゃないかと思っています。 id:ku-ma-me さんと PerlRuby がどっちが強いかみたいな話をしていたのですが、この問題なんかは割と象徴的な気がします。

Ruby はまんべんなく強くて、引き出しが多くて優秀な武器がそこらじゅうにある感じ。 Perl は火縄銃と核兵器

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h