フィボナッチ数列の作り方
TRANSCRIPT
フィボナッチ数列の作り方
2013/2/2
cuzic
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
自己紹介
最近のできごと
Dell XPS 12 を買いました。
不思議なギミックでタブレットになる
Full HD、Touch Panel、Display Portポート
Office 365 Home Premium に契約しました
毎月 $9.99 で Office の最新版を利用可能
パワポ、エクセル、ワードを最大5台にインストール可能
(2013年2月2日現在)日本では、まだサービス開始していません。。。
今後の勉強会
プログラマのためのサバイバルマニュアル読書会 第1回
3月2日 JR尼崎駅 徒歩2分 小田公民館で開催
11
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
22フィボナッチ数列について
次の漸化式を満たす数列をフィボナッチ数列という
フィボナッチ数列の特徴
うさぎの増え方を表現する漸化式
𝐹𝑛−1
𝐹𝑛が 黄金比(
5−1
2= 0.683…) に収束する
プログラミングにおけるフィボナッチ数列
関数型プログラミング言語における Hello, World!
入門向けに使える程度の複雑さ
切り口によって、いろんな説明ができる
今回は、フィボナッチ数列の求め方をいろいろ説明します。
𝑭𝒏 = 𝟎𝟏
𝑭𝒏−𝟏 + 𝑭𝒏−𝟐
𝒏 = 𝟎𝒏 = 𝟏𝒏 ≥ 𝟐
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
単純な実装 33
a = 0; b = 1loop do
a, b = b, a + bbreak if 100 < aputs a
end
■ (1) もっとも単純な実装 ■ (2) 多重代入の利用
もっとも単純なフィボナッチ数列の Ruby による実装
ポイント: ループ、多重代入、while の構文
a = 0; b = 1loop do
tmp = aa = bb = tmp + bbreak if 100 < aputs a
end
a = 0; b = 1while a < 100
puts ba, b = b, a + b
end
■ (3) while の利用
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
ブロック付メソッド 44
■ (4) ブロック付メソッド呼び出し ■処理の実施順
yield キーワードにより、ブロック引数に値を渡せる。
ブロック付メソッド呼出しにより、値の生成と、値の利用の処理を分離できる
def fibonaccia = 0; b = 1loop doa, b = b, a + byield a
endend
n = 1fibonacci do |i|
break if 100 < aputs “#{n} #{i}"n += 1
end
①
②③
⑤
⑦
⑤ → ①→ ② → ⑦ → ③→ ② → ⑦ → ③→ ② → ⑦ → ③・・・ (以下おなじ)
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クラスの利用 55
■ (5) クラスの利用
クラスを利用し、内部状態をインスタンス変数に持たせる方法もある
この場合は、next を実行したときに内部状態が変化する。
class Fibonaccidef initialize@a = 0; @b = 1
end
def next@a, @b = @b, @a + @breturn @a
endendfibonacci = Fibonacci.new
loop doi = fibonacci.next()break if i > 100puts i
end
インスタンス変数の初期化インスタンス変数の初期化
次の値の生成次の値の生成
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クロージャ 66
■ (6) クロージャの利用
クロージャという機能により、ブロックの中からネストの外側の変数を参照できる。
内部状態を持つ関数を簡易に作ることができる。
fibonacci = proc doa = 0; b = 1lambda doa, b = b, a + breturn a
endend.call()
loop doi = fibonacci.()break if i > 100puts i
end
スコープを導入するためのクロージャスコープを導入するためのクロージャ
外側のクロージャを呼ぶ出すための Proc#call外側のクロージャを呼ぶ出すための Proc#call
内側のクロージャを作るための lambda中で return を使いたいので 「 lambda 」にしている。内側のクロージャを作るための lambda中で return を使いたいので 「 lambda 」にしている。
クロージャを呼び出し、Fibonacci 数列の次の値を取得するクロージャを呼び出し、Fibonacci 数列の次の値を取得する
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Ruby 1.8: Generator の利用 77
■ (7) Generator の利用
Ruby 1.8 には、標準ライブラリに Generator があった
内部実装に callcc を使うので、ちょいと遅い
require 'generator'
g = Generator.new do |yielder|a = 0; b = 1loop doa, b = b, a + byielder.yield a
endend
g.each do |i|break if i > 100puts i
end
Generator が生成する項を順に取得するGenerator が生成する項を順に取得する
次の値を生成する次の値を生成する
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Ruby 1.9: Enumerator の利用 88
■ (8) Enumerator の利用
Ruby 1.9 では Enumerator を利用可能
内部実装に Fiber を使うため、高速
Enumerable モジュールのメソッド(each_cons など)を利用可能
e = Enumerator.new do |yielder|a = 0; b = 1loop doa, b = b, a + byielder << a
endend
e.each do |i|break if i > 100puts i
end
Enumerator が生成する項を順に取得するEnumerator が生成する項を順に取得する
次の生成する値を yield する次の生成する値を yield する
Enumerator による実装は、利用側のコード行数がコンパクトになる。
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考) Enumerator と Fiber 99
e = Enumerator.new do |yielder|a = 0; b = 1loop do
a, b = b, a + byielder << a
endend
e.each do |i|break if 100 < iputs i
end
■ (8) 《再掲》 Enumerator の例
fib = Fiber.new doa = 0; b = 1loop do
a, b = b, a + bFiber.yield a
endend
def fib.eachloop do
yield self.resumeend
end
fib.each do |i|break if 100 > iputs i
end
■ 《参考》 (9) Fiberの例
Enumerator は Fiber を活用するデザインパターンの1つFiber における難しい概念を隠ぺいFiber.yield => Enumerator::Yielder#<< 、 Enumerator#each による列挙
Enumerator を使いこなせれば、十分 Fiber のメリットを活用できる《参考: Fiber にできて、Enumerator でできないこと》
親 Fiber から 子Fiber へのデータの送付(Fiber.yield の返り値の利用)
これが必要。だけど、これが難しい。(汗)
Enumerator の内部実装まで考えなくていい
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerator はベンリ 1010
fib = Enumerator.new do |yielder|a = 0; b = 1loop do
a, b = b, a + byielder << a
endend
fib.each do |i|break if 100 < Iputs i
end
fib.each_cons(2) do |i, j|break if 100 < iputs "#{i} #{j}"
end
■ (10) Enumerator の例
def fibonaccia = 0; b = 1loop do
a, b = b, a + byield a
endend
fibonacci do |i|break if 100 < iputs i
end
prev = nilfibonacci do |i|
break if 100 < iputs "#{prev} #{i}" if prevprev = i
end
■ 《参考》 (11) ブロック付メソッド呼び出しの例
Enumerator なら Enumerable の便利メソッドを使える。ブロック付メソッド呼び出しは単純なイテレーションならいいけど。。。
Enumerator はオブジェクトなので、使い回しが簡単。引数にしたり、返り値にしたり、インスタンス変数に格納したり
Enumerator オブジェクトの生成も慣れれば簡単。
慣れれば、カンタン
面倒 ! !
Enumerable
の便利メソッドを使い放題
単純なイテレーションならいいけど・・・。
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerable#lazy の利用 1111
fib = Enumerator.new do |yielder|a = 0; b = 1loop doa, b = b, a + byielder << a
endend
fib.select do |i|i < 100
end.take(10).each do |i|puts i
end
■ (12) うまく動かない例
fib = Enumerator.new do |yielder|
a = 0; b = 1loop do
a, b = b, a + b
yielder << aend
end
fib.lazy.select do |i|
i < 100
end.take(10).each do |i|puts i
end
■ (13) Enumerable#lazy を利用する例
Ruby 2.0.0 から、Enumerable#lazy が利用可能となるEnumerable#lazy を使うと、遅延評価になり、無限を無限のまま扱える
宇宙ヤバい
Ruby 1.9 でも gem でインストール可能
@yhara さんの作品
ここで、無限ループになり、うまく動かない。
※ take_while を使う方法もある。
Enumerable#lazy
を使えば、正しく期待通りに動作する
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考)Enumerable#lazy の実装(Ruby 2.0での実際の実装とは異なる) 1212
module Enumerabledef lazy
Enumerator::Lazy.new(self)end
end
class Enumeratorclass Lazy < Enumerator
def initialize(obj, &block)super(){|yielder|
beginobj.each{|x|
if blockblock.call(yielder, x)
elseyielder << x
end}
rescue StopIterationend
}end
def select(&block)Lazy.new(self){|yielder, val|
if block.call(val)yielder << val
end}
end
def take(n)taken = 0Lazy.new(self){|yielder, val|
if taken < nyielder << valtaken += 1
elseraise StopIteration
end}
end……
end
Enumerable#lazy を使うと、Enumerable のメソッドの返り値がEnumerator::Lazy オブジェクトになる
Enumerable モジュールに増やすメソッドは Enumerable#lazy だけ
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
単純な再帰と末尾再帰
フィボナッチ数列を再帰で求めてみる
関数型プログラミング言語の入門には、ほぼ必ず登場する例
適度な難易度で説明しやすい
末尾再帰による高速化、遅延評価とかを説明できる
1313
# めちゃくちゃ速い : O(n) の速さ
def fib n, a = 0, b = 1return a if n == 0return fib(n-1, b, a+b)
end# def fib_nonrecursive n# a = 0; b = 1# loop do# return a if n == 0# n, a, b = n-1, b, a+b# end# end
(0..30).each do |n|puts fib(n)
end
# めちゃくちゃ遅い (指数関数オーダ)
def fib nreturn n if n <= 1return fib(n-1) + fib(n-2)
end
(0..30).each do |n|puts fib(n)
end
■ (14) 単純な再帰、n項を取得 ■ (15) 再帰の例(高速版)
末尾再帰のコードとまったく同じ処理を再帰を使わずに書くとこうなる。
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
再帰と memoize
memoize という高速化手法がある最初の一度だけ計算し、2回目以降は前回計算済みの値を返す
メモリを多く消費するが、計算時間を短縮できる
同じ計算を繰り返すときは、コストに見合う高速化効果が得られる
遅い再帰でも memoize することで、大幅に高速化できる
1414
# memoize版。同じ計算は1度だけ。圧倒的に速い。def add a, b
memo = nillambda do
return memo if memomemo = a.() + b.()return memo
end end
fib = Enumerator.new do |yielder|a = lambda{ 0 }b = lambda{ 1 }loop do
a, b = b, add(a, b)yielder << a
endend
fib.take(36).each do |i|puts i.()
end
# memoize しない場合。同じ計算を何度もする。# すごい遅い。
def add a, blambda do
return a.() + b.()end
end
fib = Enumerator.new do |yielder|a = lambda{ 0 }b = lambda{ 1 } loop do
a, b = b, add(a, b)yielder << a
endend
fib.take(36).each do |i|puts i.()
end
■ (16) memoize しない例 ■ (17) memoize する例
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
1515まとめ
単純な問題だけど、いろんな解法がある
ループ、ブロック付メソッド、クラス、クロージャ、Enumerator、再帰
個人的オススメは、Enumerator による実装
利用側のコードがコンパクト
Enumerable モジュールの便利メソッドが使える
Enumerable#lazy とか
再帰はとくに実装がコンパクトになる
ただし、注意しないと非常に遅い実装になることもある
遅いときも下記の指針に従うことで、高速化できる。
更新が必要な内部状態を引数に持ってくるように引数を見直す
memoize を使って、計算を一度だけにする。
1616
ご清聴ありがとうございました