Rubyのrepeat関数でフィボナッチ、トリボナッチ、テトラナッチ!

1または複数の初期値に任意の関数を繰り返し適用して
その結果のリストを返す汎用関数repeatを定義しよう

def repeat(f, *args)
  Enumerator.new { |y| loop { y << (*args = f[*args]) } }
end

repeatは関数f*1とfの初期値となるargsを引数に取り
Enueratorオブジェクトを返す


Enumeratorのブロックの中では
loopによってargsを関数fに適用した結果が
繰り返しyつまりEnumerable::Yielderオブジェクトに渡される


次にフィボナッチだ

def fib
  ->a,b{ [b, a+b] }
end

fib関数は数列上の並んだ2つの数a,bを取り
次の並びとしてその上位の数bとa,bの和の組を返す


さあ
今作成した2つの関数repeatとfibを使って
フィボナッチ数列の第20位までを求めよう

fibonacci = repeat(fib, 0, 1)
fibonacci.take(20).map(&:first) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]


もちろんfib関数は高階関数である必要はない
通常の関数でいい

def fibm(a, b)
  [b, a+b]
end


ただしこの場合は関数をオブジェクト化して
repeat関数に渡す必要がある

fibonacci = repeat(method(:fibm), 0, 1)
fibonacci.take(20).map(&:first) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]


次はトリボナッチだ*2
フィボナッチが前2項の和を取るのに対して
トリボナッチは前3項の和を取る

def tribo
  ->a,b,c{ [b, c, a+b+c] }
end


これをrepeat関数に渡して結果を得よう

tribonacci = repeat(tribo, 0, 0, 1)
tribonacci.take(20).map(&:first) # => [0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149, 274, 504, 927, 1705, 3136, 5768, 10609, 19513, 35890]


そしてテトラナッチだ
テトラナッチは前4項の和を取る

def tetra
  ->a,b,c,d{ [b, c, d, a+b+c+d] }
end


同じくrepeat関数に渡して結果を得よう

tetranacci = repeat(tetra, 0, 0, 0, 1)
tetranacci.take(20).map(&:first) # => [0, 0, 1, 1, 2, 4, 8, 15, 29, 56, 108, 208, 401, 773, 1490, 2872, 5536, 10671, 20569, 39648]


このように1つのrepeat関数を定義することで
フィボナッチ、トリボナッチ、テトラナッチの各数列を
容易に導きだすことができた


もちろん
repeat関数の応用範囲は数列の算出に留まらない
ニュートンラプソン法による平方根の算出は次のようになる

def sqrt
  ->n,x{ (x + n/x) / 2.0 }.curry
end

sqrt3 = repeat(sqrt[3], 1.0).take(10).last # => 1.7320508075688772
sqrt5 = repeat(sqrt[5], 1.0).take(10).last # => 2.23606797749979
sqrt7 = repeat(sqrt[7], 1.0).take(10).last # => 2.6457513110645907


(追記:2011-7-1)
さらに進んでEnumerable#repeatというのはどうだろうか

module Enumerable
  def repeat
    x = self
    Enumerator.new { |y| loop { y << (x = yield x) }}
  end
end

Enumerable#repeatはselfに初期値を与え
ブロックで繰り返し適用する関数を定義する


このメソッドを使った先の演算は以下のようになる

fibonacci = [0,1].repeat { |a, b|  [b, a+b] }

fibonacci.take(20).map(&:first) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

tribonacci = [0,0,1].repeat { |a,b,c|  [b, c, a+b+c] }

tribonacci.take(20).map(&:first) # => [0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149, 274, 504, 927, 1705, 3136, 5768, 10609, 19513, 35890]

tetranacci = [0,0,0,1].repeat { |a, b, c, d|  [b, c, d, a+b+c+d] }

tetranacci.take(20).map(&:first) # => [0, 0, 1, 1, 2, 4, 8, 15, 29, 56, 108, 208, 401, 773, 1490, 2872, 5536, 10671, 20569, 39648]

sq5 = [5, 1.0].repeat { |n, x|  [n, (x + n/x) / 2.0] }

sq5.take(10).last.last # => 2.23606797749979


このほうがRubyっぽいかもしれない


関連記事:

  1. Rubyでフィボナッチ、トリボナッチ、テトラナッチ!そして僕はヒトリボッチ(2009-01-11)
  2. Rubyを使って「なぜ関数プログラミングは重要か」を解読しよう!(その3)(2011-02-01)

*1:正確には[]メソッドでcallされる手続きオブジェクト

*2:http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0