名古屋で数学するプログラマ(仮)

@antimon2 が趣味兼一部本職の数学で何かするときのブログ。

文字と数値リテラルを使わない Hello World 考 (in Ruby)

CodeIQ の大人気問題「Restricted Words」が、ついに解答締切となりました。

この問題は「数値・文字・文字列リテラルを使用せずに、"Hello World" と出力する」というもの。
解答締切と共に、出題者から解答集が公開されました。

色々な言語の解答が載っていて、見ているだけで楽しいですね(^-^)

さて、でもここにのっている解答は、基本、以下のパターンです。

  • キーワードや配列リテラルを利用して、そのサイズ(や 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 リテラル表記です。
他に「:"〜"」という書き方も出来ます↓。

ぱっと見「これ、文字列そのまま使ってるじゃん」て怒られそうですが「違うよ、『:"〜"』はシンボルであって文字列じゃないよ」と、言えなくはないですが…。
やっぱり、なんかずるしてる感がイナメナイですよね(^-^;

おまけ:変数すら使わない例

最後に、初心に返って「何らかの方法で数値を生成して、そこから文字列を生成」という王道の別解を。

配列リテラルの他は、Kernel#puts, Array#size, Integer#chr の3つのメソッドと文字列連結演算子(String#+)しか使ってません!変数すら利用していません!シンプル!
多くの他言語で同じようなことが実現可能ですよね。

*1:実際、解答集サイトには、Perl の barewaord を利用した例、Ruby の class オブジェクトからクラス名を取得できることを利用した例、C の 関数名を文字列で取得できることを利用した例、などが挙げられています。

*2:Ruby のバージョン(内部で利用している正規表現エンジンのバージョン?)によって結果が多少異なりますが、その中に「Hello World」という文字列が含まれていることは同じです。

*3:ちなみに Ruby の String は mutable、つまり変更可能です。