4. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
単純な実装 33
a = 0; b = 1
loop do
a, b = b, a + b
break if 100 < a
puts a
end
■ (1) もっとも単純な実装 ■ (2) 多重代入の利用
もっとも単純なフィボナッチ数列の Ruby による実装
ポイント: ループ、多重代入、while の構文
a = 0; b = 1
loop do
tmp = a
a = b
b = tmp + b
break if 100 < a
puts a
end
a = 0; b = 1
while a < 100
puts b
a, b = b, a + b
end
■ (3) while の利用
5. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
ブロック付メソッド 44
■ (4) ブロック付メソッド呼び出し ■ 処理の実施順
yield キーワードにより、ブロック引数に値を渡せる。
ブロック付メソッド呼出しにより、
値の生成と、値の利用の処理を分離できる
def fibonacci
a = 0; b = 1
loop do
a, b = b, a + b
yield a
end
end
n = 1
fibonacci do |i|
break if 100 < a
puts “#{n} #{i}"
n += 1
end
①
②
③
⑤
⑦
⑤ → ①
→ ② → ⑦ → ③
→ ② → ⑦ → ③
→ ② → ⑦ → ③
・・・ (以下おなじ)
6. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クラスの利用 55
■ (5) クラスの利用
クラスを利用し、内部状態をインスタンス変数に持たせる方法もある
この場合は、next を実行したときに内部状態が変化する。
class Fibonacci
def initialize
@a = 0; @b = 1
end
def next
@a, @b = @b, @a + @b
return @a
end
end
fibonacci = Fibonacci.new
loop do
i = fibonacci.next()
break if i > 100
puts i
end
インスタンス変数の初期化インスタンス変数の初期化
次の値の生成次の値の生成
7. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クロージャ 66
■ (6) クロージャの利用
クロージャという機能により、ブロックの中から
ネストの外側の変数を参照できる。
内部状態を持つ関数を簡易に作ることができる。
fibonacci = proc do
a = 0; b = 1
lambda do
a, b = b, a + b
return a
end
end.call()
loop do
i = fibonacci.()
break if i > 100
puts i
end
スコープを導入するためのクロージャスコープを導入するためのクロージャ
外側のクロージャを呼ぶ出すための Proc#call外側のクロージャを呼ぶ出すための Proc#call
内側のクロージャを作るための lambda
中で return を使いたいので 「 lambda 」にしている。
内側のクロージャを作るための lambda
中で return を使いたいので 「 lambda 」にしている。
クロージャを呼び出し、Fibonacci 数列の次の値を取得するクロージャを呼び出し、Fibonacci 数列の次の値を取得する
8. 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 = 1
loop do
a, b = b, a + b
yielder.yield a
end
end
g.each do |i|
break if i > 100
puts i
end
Generator が生成する項を順に取得するGenerator が生成する項を順に取得する
次の値を生成する次の値を生成する
9. 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 = 1
loop do
a, b = b, a + b
yielder << a
end
end
e.each do |i|
break if i > 100
puts i
end
Enumerator が生成する項を順に取得するEnumerator が生成する項を順に取得する
次の生成する値を yield する次の生成する値を yield する
Enumerator による実装は、
利用側のコード行数がコンパクト
になる。
10. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考) Enumerator と Fiber 99
e = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
e.each do |i|
break if 100 < i
puts i
end
■ (8) 《再掲》 Enumerator の例
fib = Fiber.new do
a = 0; b = 1
loop do
a, b = b, a + b
Fiber.yield a
end
end
def fib.each
loop do
yield self.resume
end
end
fib.each do |i|
break if 100 > i
puts i
end
■ 《参考》 (9) Fiberの例
Enumerator は Fiber を活用するデザインパターンの1つ
Fiber における難しい概念を隠ぺい
Fiber.yield => Enumerator::Yielder#<< 、 Enumerator#each による列挙
Enumerator を使いこなせれば、十分 Fiber のメリットを活用できる
《参考: Fiber にできて、Enumerator でできないこと》
親 Fiber から 子Fiber へのデータの送付(Fiber.yield の返り値の利用)
これが必要。
だけど、これが
難しい。(汗)
Enumerator の
内部実装まで
考えなくていい
11. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerator はベンリ 1010
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
fib.each do |i|
break if 100 < I
puts i
end
fib.each_cons(2) do |i, j|
break if 100 < i
puts "#{i} #{j}"
end
■ (10) Enumerator の例
def fibonacci
a = 0; b = 1
loop do
a, b = b, a + b
yield a
end
end
fibonacci do |i|
break if 100 < i
puts i
end
prev = nil
fibonacci do |i|
break if 100 < i
puts "#{prev} #{i}" if prev
prev = i
end
■ 《参考》 (11) ブロック付メソッド呼び出しの例
Enumerator なら Enumerable の便利メソッドを使える。
ブロック付メソッド呼び出しは単純なイテレーションならいいけど。。。
Enumerator はオブジェクトなので、使い回しが簡単。
引数にしたり、返り値にしたり、インスタンス変数に格納したり
Enumerator オブジェクトの生成も慣れれば簡単。
慣れれば、
カンタン
面倒 ! !
Enumerable
の便利メソッド
を使い放題
単純なイテレーション
ならいいけど・・・。
12. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerable#lazy の利用 1111
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
fib.select do |i|
i < 100
end.
take(10).each do |i|
puts i
end
■ (12) うまく動かない例
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
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
を使えば、正しく
期待通りに動作する
13. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考)Enumerable#lazy の実装(Ruby 2.0 での実際の実装とは異なる) 1212
module Enumerable
def lazy
Enumerator::Lazy.new(self)
end
end
class Enumerator
class Lazy < Enumerator
def initialize(obj, &block)
super(){|yielder|
begin
obj.each{|x|
if block
block.call(yielder, x)
else
yielder << x
end
}
rescue StopIteration
end
}
end
def select(&block)
Lazy.new(self){|yielder, val|
if block.call(val)
yielder << val
end
}
end
def take(n)
taken = 0
Lazy.new(self){|yielder, val|
if taken < n
yielder << val
taken += 1
else
raise StopIteration
end
}
end
……
end
Enumerable#lazy を使うと、Enumerable のメソッドの返り値が
Enumerator::Lazy オブジェクトになる
Enumerable モジュールに増やすメソッドは Enumerable#lazy だけ
14. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
単純な再帰と末尾再帰
フィボナッチ数列を再帰で求めてみる
関数型プログラミング言語の入門には、ほぼ必ず登場する例
適度な難易度で説明しやすい
末尾再帰による高速化、遅延評価とかを説明できる
1313
# めちゃくちゃ速い : O(n) の速さ
def fib n, a = 0, b = 1
return a if n == 0
return 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 n
return n if n <= 1
return fib(n-1) + fib(n-2)
end
(0..30).each do |n|
puts fib(n)
end
■ (14) 単純な再帰、n項を取得 ■ (15) 再帰の例(高速版)
末尾再帰のコードと
まったく同じ処理を
再帰を使わずに
書くとこうなる。
15. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
再帰と memoize
memoize という高速化手法がある
最初の一度だけ計算し、2回目以降は前回計算済みの値を返す
メモリを多く消費するが、計算時間を短縮できる
同じ計算を繰り返すときは、コストに見合う高速化効果が得られる
遅い再帰でも memoize することで、大幅に高速化できる
1414
# memoize版。同じ計算は1度だけ。圧倒的に速い。
def add a, b
memo = nil
lambda do
return memo if memo
memo = 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
end
end
fib.take(36).each do |i|
puts i.()
end
# memoize しない場合。同じ計算を何度もする。
# すごい遅い。
def add a, b
lambda 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
end
end
fib.take(36).each do |i|
puts i.()
end
■ (16) memoize しない例 ■ (17) memoize する例