数字もまぁなんか消せると思う。
任意の Ruby プログラムをアルファベットと数字だけにするプログラム
とサラッと言われているのだけれど、思いつくのにかなり苦労しました。なんとか 2 つの方法を見つけた。
出題から 1 年半以上経っているので、以下にぼくの回答を書きます。みたくない人は見ないでください。
ポイントは、任意の文字列を構成する各 ASCII コードをどうやってアルファベットだけで作るか、というところです。
Hello, world
class String def inspect concat begin dup ensure replace String nil concat concat concat concat size concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat concat concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat size concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat concat size concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat size concat concat size concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat size concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat size concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat size concat concat size concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat size concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat size concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat concat concat size concat concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat concat size concat concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat concat size concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat concat concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat concat size concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat size concat concat concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat size concat concat concat size concat size concat begin size ensure replace String nil end end concat begin dup ensure replace String nil concat concat size concat concat size concat concat concat concat concat size concat begin size ensure replace String nil end end eval self exit end end copyright MMX Yusuke Endoh p String nil
caller と for を使う方法
最初に思いついたのはこの方法。caller を使って所望の長さの配列を作り、その size を呼び出すことで任意の数値を得ます。
def foo1; caller; end def foo2; foo1; end def foo3; foo2; end def foo4; foo3; end foo4.size #=> 4 (呼び出したコンテキストによって多少ずれる)
しかし size がそのままでは呼び出せない (ドットを使ってしまう) ので、for 文を使います。
for i in ary do ... end
と
ary.each {|i| ... }
は (多分) 等価というのは知っていたんですが、返り値まで一緒というのは初めて知りました。つまり for 文を使うと each を呼び出し、その返り値を得ることができます。
以上をまとめると、以下のようになります。
require "erb" code = "puts 'Hello, world!'" ERB.new(DATA.read, nil, "%").run __END__ class Array def each size end end class String def aaaaabb caller end % (4..127).each do |x| def <%= ("%07b" % x).tr("01", "ab") %> <%= ("%07b" % (x - 1)).tr("01", "ab") %> end % end def inspect % code.each_byte do |x| concat for i in <%= ("%07b" % x).tr("01", "ab") %> do end % end eval self exit end end p String nil
ただ、この方法は caller のために 127 個くらいのメソッドを定義するので、そこがかっこ悪いです。
ensure 節を使う方法
もうひとつは、String#inspect の中で所望の長さの文字列を作り、その size を呼び出すことで任意の数値を得る方法。上の Hello, world はこっちの方法を整形したものです。
class String def inspect replace "" concat size concat size concat size ... size end end
concat size の連続の部分は、concat self をあわせて使えば長さを 2 倍にできることを使えば短くできます。
ただ、これと同じ方法 (String#inspect の中で concat) でコード本体も作るので、単純に replace "" してしまうと今まで作ってきたコード本体が消えてしまいます。そこで ensure 節を利用します。
p begin; 1; ensure; 2; end
というプログラムが 1 を返す (2 は捨てられる) というのを活用して、以下のようにします。
concat begin dup ensure replace "" concat size concat size concat size ... concat begin size ensure replace "" end end
口では説明しかねますが、評価順序がうまい感じになっていて、文字列の先頭に任意の数値を追加するコードになっています。
以上をまとめると、以下のようになります。
require "erb" code = "puts 'Hello, world!'" ERB.new(DATA.read, nil, "%").run __END__ class String def inspect % code.each_byte.to_a.reverse_each do |x| concat begin dup ensure replace String nil % ("%b" % x)[0..-2].scan(/10*/) do |b| <%= "concat " * b.size %>concat size % end % if x % 2 == 1 concat size % end concat begin size ensure replace String nil end end % end eval self exit end end p String nil
open problem
小文字アルファベットだけ、は無理かなあ。