最近不適切にFizzBuzzをやるのが楽しく、常にFizzBuzzのことばかり考えている気がします。これが"恋"というものなのでしょうか。
というわけなので、最近書いたFizzBuzzをいくつか紹介したいと思います。
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の活用
(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のデフォルト値を利用
(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
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の書きかたをどうぞ。
method_missing.rb
method_missing と define_method の活用
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
__,__,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
した結果を見てみましょう。
eval "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> ""
eval "%% %%%% %%%% %%%% %%%%%%%%%%%%%%%%%%%%%%%%%"
#=> " "
eval "%% %%%%%%%%%%%%%%%% %%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> " "
eval "%% %%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
#=> " "
ご覧のように、0個以上の連続した半角スペースにて構成される文字列が返っています。このからくりは文字列リテラルとString#%
メソッドにあります。
%% %%%% %%%% %%%% %%%%%%%%%%%%%%%%%%%%%%%%%
このコードは以下の様に書き換えることができます。
%% %.%(
%%%.%(
%% %.%(
%%%.%(
%%%.%(
%% %.%(
%%%.%(
%%%.%(
%% %.%(
%%%.%(
%%%.%(
%%%.%(
%%%.%(
%%%.%(
%%%.%(%%%)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
まだわかりにくいですか?それでは更に以下の様に書き換えてみましょう。
%` `.%(
%``.%(
%` `.%(
%``.%(
%``.%(
%` `.%(
%``.%(
%``.%(
%` `.%(
%``.%(
%``.%(
%``.%(
%``.%(
%``.%(
%``.%(%``)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
いいですね。実は%%%
や%% %
は単なる文字列のリテラル、それ以外の%
は文字列のフォーマット用メソッドだったのです。%
メソッドは演算子のように使えるシンタックスシュガーが導入されており、更に演算子と対象の識別子との間に半角スペースを置く必要がないので、単なる%
の羅列の様に見せかけることができるのです。その中に半角スペースを混ぜることでアスキーアートの様にしていたというのが種明かしになります。
あとは簡単ですね。eval
によって得られた文字列のsize
によりいくつかの英数を得ることができるので、あとはそれをこねくり回してFizzBuzzを構成するだけです。数字から文字列を作るにはArray#pack
メソッドを使いましょう。
[70, 105, 122, 122, 66, 117, 122, 122].pack 'c*'
#=> "FizzBuzz"
おわり
以上になります。皆さんもぜひ、不適切なFizzBuzzを書いてみてください。楽しいですよ。