文字と数値リテラルを使わない Hello World 考 (in Ruby)
CodeIQ の大人気問題「Restricted Words」が、ついに解答締切となりました。
この問題は「数値・文字・文字列リテラルを使用せずに、"Hello World" と出力する」というもの。
解答締切と共に、出題者から解答集が公開されました。
解答集を公開します https://t.co/fd418ZJrAp:挑戦者求む!Restricted Words by @cielavenir http://t.co/6uourFB4uJ @codeiqさんから
— しえる (@cielavenir) 2013, 9月 19
色々な言語の解答が載っていて、見ているだけで楽しいですね(^-^)
さて、でもここにのっている解答は、基本、以下のパターンです。
- キーワードや配列リテラルを利用して、そのサイズ(や length 等)から数値を生成する。
- 数値から文字を生成する。
でも最低条件は、「数値・文字・文字列リテラルを使用しないこと」。
この基本パターン以外の解答も探せばいくらでも見つかります*1。
ということで、私の提出した解答例と共に、それ以外のものも含めた Ruby での「別解」を探ってみたいと思います。
クラスオブジェクトを利用した例
まずは私の提出した解答。
これは、解答集にもある「Ruby の class オブジェクトからクラス名が得られることを利用した解答例」になっています。
あと別に求められていないのですがコードゴルフを意識してなるべく短く書いてみました。提出するにあたっての利便性から1行目に「#!/usr/bin/env ruby」と付けていますが、それを取っちゃえばワンライナーです。
ポイントは、Ruby の Class クラスの仕様(正確には、その親クラスである Module クラスの仕様)として、以下のようになっていることです。
- .name メソッドは、そのクラス(モジュール)名が大文字で始まる場合に、そのクラス(モジュール)名を返す。そうでない場合は、nil を返す。
- .to_s メソッドは、.name メソッドが nil 以外を返す場合にその文字列をそのまま返す。そうでない場合は、Object#to_s と同じ仕様。
早い話、普通に宣言したクラスは、.to_s するとそのクラス名を返す、ということです。
このコードのそれ以外の部分を簡単に解説しておきます。
- Hello、World という2つのクラスを用意する。
- ↑の2つのクラスオブジェクトを格納した配列 a を用意する。
a.size == 2
なので、a.size<<(a+a).size == 2<<4 == 32
で半角空白の文字コード値が生成できる。→ .chr メソッドで半角空白1文字からなる文字列が得られる。- a を半角空白で .join することで、配列の各要素を文字列化し半角空白で繋げるので、結果として "Hello World" という文字列が得られる(のでそれをそのまま出力する)。
実は短くすることが目的なら、上述の仕様の通りで以下のように書くことも出来ます。
なんで class Hello;self;end
という書き方や、Hello=Class.new
という書き方で OK なのか。
それは話すと長くなるので省略しますが、Ruby の メタプログラミング を勉強すれば理解できるようになると思います。
正規表現を利用した例
条件は「数値・文字・文字列リテラル禁止」、とだけあって、それ以外のリテラルには触れていません。
だから解答例でも私の解でも、配列とか TRUE とかそういうのは使っちゃってますよね。
じゃ、正規表現リテラルも使用は OK なんでしょうか?
それが OK なら、Ruby ならこんな書き方が出来ます。
簡単に解説。
- /Hello World/ という正規表現を変数 r に代入。
- ↑を文字列化(.to_s)する。⇒"(?-mix:Hello World)" という文字列が得られる*2。
- ↑で得られた文字列を元の正規表現 r でマッチする(
=~r
)⇒この式の値は、マッチした文字列の index、この場合 7。もしマッチに失敗したら nil が返る。 - マッチに成功したら $& という組込変数にマッチした文字列全体が入るので、それを出力する(「
&&
」は論理積ですが、この場合は「左側が false や nil でない → 右側を評価」という流れになるわけです)
正規表現リテラルを用意している言語(Perl, JavaScript 等)なら、たぶん似たような書き方ができると思います。
シンボルって使っていいの?
ところで、解答集サイトの「Ruby の class を利用した別解」では、2行目が以下のようになっています。
require :digest.to_s
これは "digest" という標準添付ライブラリをロードして、Digest::SHA2.new.size
が 32 であることを利用して半角空白を生成しているのですが。
この「:digest
」という部分。これ、シンボルです。
シンボル (Symbol) というのは、まー色々言うことは出来るのですが、誤解を恐れずに一言で言うと、
Ruby における Symbol は、変更の出来ない(immutable な) String
です*3。
なので、文字列とほぼ同じ扱いなのでこれも使っちゃだめだろ、と思ってたのですが。
解答例で堂々と使ってますよね(^-^;
これ使っていいんだったら、先ほどのクラスの例を↓こんな風にもっと簡潔に書けます。
(ポイント:Symbol#to_s メソッドは、シンボルに 1 対 1 に対応する文字列を返す)
もしくは、Symbol オブジェクトをそのまま puts に渡せばそのまま対応する文字列を出力するので、↓こんなこともできます。
「%s[〜]」は、中身をそのまま値とする Symbol リテラル表記です。
他に「:"〜"」という書き方も出来ます↓。
ぱっと見「これ、文字列そのまま使ってるじゃん」て怒られそうですが「違うよ、『:"〜"』はシンボルであって文字列じゃないよ」と、言えなくはないですが…。
やっぱり、なんかずるしてる感がイナメナイですよね(^-^;