Rubyによる不適切なFizzBuzzの世界

最近不適切にFizzBuzzをやるのが楽しく、常にFizzBuzzのことばかり考えている気がします。これが"恋"というものなのでしょうか。

というわけなので、最近書いたFizzBuzzをいくつか紹介したいと思います。

## `basic.rb` 基本パターン

```basic.rb
(1..100).each do |n|
  puts case 0
  when n % 15 then :FizzBuzz
  when n % 3 then :Fizz
  when n % 5 then :Buzz
  else n
  end
end
```

いいですね。ポイントは`case`の使い方です。caseは、caseに渡したオブジェクトと、各when節の評価結果を`===`メソッドで比較します。例えばFizzBuzzに該当するかの判定部分は`0 === n % 15`といった処理が走ることになるわけですね。

## `tap.rb` tap with breakの活用

```tap.rb
(1..100).map{|_|
  _.tap{|_|
    break :FizzBuzz if _ % 15 == 0
    break :Fizz if _ % 3 == 0
    break :Buzz if _ % 5 == 0
  }
}.map &method(:puts)
```

tapは基本的にブロックの評価値を捨てますが、`break`した場合に限り副作用を与えます。それを利用し、FizzBuzz, Fizz, Buzzのいずれかの条件に当てはまる場合のみ`break`しています。

## `default_value.rb` Hashのデフォルト値を利用

```default_value.rb
(1..100).map(
  &->_{{0=>:FizzBuzz,3=>:Fizz,5=>:Buzz,6=>:Fizz,9=>:Fizz,10=>:Buzz,12=>:Fizz}.fetch _%15,_}
).map &method(:puts)
```

`Hash#fetch`は、第一引数で指定したキーがハッシュに存在すれば対応する値を、存在しなければ第二引数で指定した値を返すメソッドです。任意の整数nを15で除算した剰余が取りうる値は0..14なので、その中で FizzBuzz, Fizz, Buzzに該当する0,3,5,6,9,10,12をキーとして持つHashを作成し、`fetch`メソッドを利用することで任意の整数についてFizzBuzzの判定を行う事ができます。

## `php.rb` PHPとしても実行可能なFizzBuzz

```php.rb
p <<'PHP_VERSION;'
<?php
PHP_VERSION;
print "\033[1F\033[1M";
//.tap{ define_method :range, -> s,e { s.upto e } }
//.tap{ define_method :array_map, -> f,seq { seq.map{ |x| f.(x) } } }
//.tap{ define_method :function, -> x,&b { -> x { $x=x;b.call } } }
array_map(function($x){
  print $x % 15 == 0 ? 'FizzBuzz' : ($x % 3 == 0 ? 'Fizz' : ($x % 5 == 0 ? 'Buzz' : $x));
  print "\n";
}, range(1,100));
```

phpとしてもrubyとしても実行できるFizzBuzzです。php.rbの詳細については[PHPとしても実行できるRubyの書きかた](http://qiita.com/supermomonga/items/79d599b4ed8b385870b0)をどうぞ。

## `method_missing.rb` method_missing と define_method の活用

```method_missing.rb
module Kernel
  define_method '0', -> idx, _ { %w`Fizz Buzz FizzBuzz`[idx] }
end
def method_missing n, _ = nil, orig = nil
  v, i = [3,5,15].map{|_|n.to_s.to_i % _}.each_with_index.sort_by{|_|[_[0],_[1]*-1]}.first
  return _ ? orig : send(v.to_s, i, n)
end
(1..100).map(&:to_s).map(&method(:send)).map(&:to_s).map(&method(:puts))
```

このFizzBuzzは数そのものをメソッド名として扱いコールすることで目的の値を得るというコンセプトのものです。`method_missing`が再帰呼び出しされ、最終的に目的の値に到達します。目的の値の取り得る値は、`呼び出しを試みたメソッド名そのもの`,`:FizzBuzz`,`:Fizz`,`Buzz`のいずれかです。

まず1〜100までの整数を文字列に変換し、その文字列をメソッド名として`Kernel`に`send`しています。`send`メソッドは、引数で指定したメソッドをレシーバにおいて実行させるメソッドです。例えば`Kernel.send :foo`は`Kernel.foo`と同義です。

そのようにして`Kernel.1`から`Kernel.100`までのメソッドが呼び出されるのですが、そもそも数字から始まるメソッド名はvalidではないので、通常は定義できませんし呼び出すこともできないのですが、`send`メソッドを経由することで呼び出すことが可能です。

しかし、当然それらのメソッドは定義されていないので、`method_missing`が呼ばれることになります。この`method_missing`では、呼び出されたメソッド名を`to_i`することでFixnumに変換した上で、3,5,15それぞれにて除算を行った際の剰余の中で最小のものを変数`v`に、`v`が0の場合は、剰余が0になった除算に応じたインデックス番号を`i`に代入します。

その後、`method_missing`の引数`_`が`nil`でない場合は`orig`の値を返すのですが、そうでない場合`Kernel`に対して`v`, `i`, `n`を引数として`send`を行います。`method_missing`の引数`_`のデフォルト値は`nil`なので、`v`の値に応じて`method_missing`が再帰するか、あらかじめ`Kernel`に定義しておいた`0`メソッドに到達することになります。`method_missing`が再帰呼び出しされた場合、その際の第二引数には`nil`でない値が渡されるので、`method_missing`によって再帰呼び出しされた`method_missing`は必ず第三引数`orig`を返却します。第三引数`orig`には元々`method_missing`が呼び出された時のメソッド名が渡されているので、結果的に3,5,15いずれの数で除算した場合も剰余が0にならない数をメソッド名として呼び出しした場合は、メソッド名そのものがそのまま返却されることになります。逆に剰余が0になる数をメソッド名として呼び出しした場合は、最終的に`Kernel.0`メソッドに到達します。`Kernel.0`メソッドは渡されたインデックス番号に従って`:FizzBuzz`,`:Fizz`,`:Buzz`いずれかの値を返すメソッドです。数字から始まる名前を持つメソッドは本来定義できませんが、`define_method`を利用することで定義することが可能です。

## `ascii_art.rb` アスキーアートから生成するFizzBuzz

```ascii_art.rb
__,__,f,i,z,z,__,__,b,u,z,z,__,__=DATA.map(&method(:eval)).map &:size
F        = (f* i+z+z+b+u+z+z) *(i**i*i-i**z)/i
  I      = (f**i+z+z+b+u+z+z) +i**i+i**z
    Z    = (f**i+z+z+b+u+z+z) +f*i+i
      Z
B        = (f* i+z+z+b+u+z+z) *(i**i*i-i**z)/i-i**i
  U      = (f**i+z+z+b+u+z+z) +f*i-i-i**z
    Z
      Z
((([[f,i,z,z,b,u,z,z].inject(&:*)]*i).inject(&:**))..(f**i+z+z+b+u+z+z)).map{|fizzbuzz|
  case [F,i,z,z,B,u,z,z].inject &:*
  when fizzbuzz %(f+f/i)     then [F,I,Z,Z,B,U,Z,Z]
  when fizzbuzz %(i+i**z)    then [F,I,Z,Z]
  when fizzbuzz %(i**i+i**z) then [B,U,Z,Z]
  else (% %s % fizzbuzz).chars.map(&:to_i).map &(f**i/i-i).method(:+)
  end.pack 'c*'
}.map &method(:puts)
__END__
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%          %%%%          %%%%          %%%%          %%%%%%%%%%%%%%%%%%%%%%%%%
%%  %%%%%%%%%%%%%%%%  %%%%%%%%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%
%%          %%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  %%%%%%%%%%%%%%%%  %%%%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  %%%%%%%%%%%%          %%%%          %%%%          %%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%        %%%%%%  %%%%%%  %%%%          %%%%          %%%
%%%%%%%%%%%%%%%%%%%%%%%%  %%%%  %%%%%%  %%%%%%  %%%%%%%%%%  %%%%%%%%%%%%  %%%%%
%%%%%%%%%%%%%%%%%%%%%%%%  %%      %%%%  %%%%%%  %%%%%%%%  %%%%%%%%%%%%  %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%  %%%%%%  %%%%  %%%%%%  %%%%%%  %%%%%%%%%%%%  %%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%          %%%%          %%%%          %%%%          %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
```

このFizzBuzzは一見すると難解に見えますが、タネさえわかってしまえば単純なものです。Rubyでは`__END__`以降の文字列は`File`オブジェクトとして`DATA`に渡されるので、`DATA.map`にて`%`および半角スペースで構成された複数の文字列をイテレートすることができます。その文字列に対して`eval`した結果に`size`メソッドを呼び出し、_,f,i,z,b,u変数にそれぞれ割り当てています。

さて、試しに`DATA`に渡る内容をいくつか`eval`した結果を見てみましょう。

```rb
eval "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> ""

eval "%%          %%%%          %%%%          %%%%          %%%%%%%%%%%%%%%%%%%%%%%%%"
#=> "          "

eval "%%  %%%%%%%%%%%%%%%%  %%%%%%%%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> "  "

eval "%%          %%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> "          "
```

ご覧のように、0個以上の連続した半角スペースにて構成される文字列が返っています。このからくりは文字列リテラルと`String#%`メソッドにあります。

```rb
%%          %%%%          %%%%          %%%%          %%%%%%%%%%%%%%%%%%%%%%%%%
```

このコードは以下の様に書き換えることができます。

```rb
%%          %.%(
  %%%.%(
    %%  %.%(
      %%%.%(
        %%%.%(
          %%  %.%(
            %%%.%(
              %%%.%(
                %%  %.%(
                  %%%.%(
                    %%%.%(
                      %%%.%(
                        %%%.%(
                          %%%.%(
                            %%%.%(%%%)
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)
```

まだわかりにくいですか？それでは更に以下の様に書き換えてみましょう。

```rb
%`          `.%(
  %``.%(
    %`  `.%(
      %``.%(
        %``.%(
          %`  `.%(
            %``.%(
              %``.%(
                %`  `.%(
                  %``.%(
                    %``.%(
                      %``.%(
                        %``.%(
                          %``.%(
                            %``.%(%``)
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)
```

いいですね。実は`%%%`や`%%  %`は単なる文字列のリテラル、それ以外の`%`は文字列のフォーマット用メソッドだったのです。`%`メソッドは演算子のように使えるシンタックスシュガーが導入されており、更に演算子と対象の識別子との間に半角スペースを置く必要がないので、単なる`%`の羅列の様に見せかけることができるのです。その中に半角スペースを混ぜることでアスキーアートの様にしていたというのが種明かしになります。

あとは簡単ですね。`eval`によって得られた文字列の`size`によりいくつかの英数を得ることができるので、あとはそれをこねくり回してFizzBuzzを構成するだけです。数字から文字列を作るには`Array#pack`メソッドを使いましょう。

```rb
[70, 105, 122, 122, 66, 117, 122, 122].pack 'c*'
#=> "FizzBuzz"
```

## おわり

以上になります。皆さんもぜひ、不適切なFizzBuzzを書いてみてください。楽しいですよ。