フィボナッチ数列の作り方

17
フィボナッチ数列の作り方 2013/2/2 cuzic

Upload: tomoya-cuzic

Post on 12-Jun-2015

2.656 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: フィボナッチ数列の作り方

フィボナッチ数列の作り方

2013/2/2

cuzic

Page 2: フィボナッチ数列の作り方

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

Page 3: フィボナッチ数列の作り方

2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」

22フィボナッチ数列について

次の漸化式を満たす数列をフィボナッチ数列という

フィボナッチ数列の特徴

うさぎの増え方を表現する漸化式

𝐹𝑛−1

𝐹𝑛が 黄金比(

5−1

2= 0.683…) に収束する

プログラミングにおけるフィボナッチ数列

関数型プログラミング言語における Hello, World!

入門向けに使える程度の複雑さ

切り口によって、いろんな説明ができる

今回は、フィボナッチ数列の求め方をいろいろ説明します。

𝑭𝒏 = 𝟎𝟏

𝑭𝒏−𝟏 + 𝑭𝒏−𝟐

𝒏 = 𝟎𝒏 = 𝟏𝒏 ≥ 𝟐

Page 4: フィボナッチ数列の作り方

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 の利用

Page 5: フィボナッチ数列の作り方

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

②③

⑤ → ①→ ② → ⑦ → ③→ ② → ⑦ → ③→ ② → ⑦ → ③・・・ (以下おなじ)

Page 6: フィボナッチ数列の作り方

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

インスタンス変数の初期化インスタンス変数の初期化

次の値の生成次の値の生成

Page 7: フィボナッチ数列の作り方

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 数列の次の値を取得する

Page 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 = 1loop doa, b = b, a + byielder.yield a

endend

g.each do |i|break if i > 100puts i

end

Generator が生成する項を順に取得するGenerator が生成する項を順に取得する

次の値を生成する次の値を生成する

Page 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 = 1loop doa, b = b, a + byielder << a

endend

e.each do |i|break if i > 100puts i

end

Enumerator が生成する項を順に取得するEnumerator が生成する項を順に取得する

次の生成する値を yield する次の生成する値を yield する

Enumerator による実装は、利用側のコード行数がコンパクトになる。

Page 10: フィボナッチ数列の作り方

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 の内部実装まで考えなくていい

Page 11: フィボナッチ数列の作り方

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

の便利メソッドを使い放題

単純なイテレーションならいいけど・・・。

Page 12: フィボナッチ数列の作り方

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

を使えば、正しく期待通りに動作する

Page 13: フィボナッチ数列の作り方

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 だけ

Page 14: フィボナッチ数列の作り方

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) 再帰の例(高速版)

末尾再帰のコードとまったく同じ処理を再帰を使わずに書くとこうなる。

Page 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 する例

Page 16: フィボナッチ数列の作り方

2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」

1515まとめ

単純な問題だけど、いろんな解法がある

ループ、ブロック付メソッド、クラス、クロージャ、Enumerator、再帰

個人的オススメは、Enumerator による実装

利用側のコードがコンパクト

Enumerable モジュールの便利メソッドが使える

Enumerable#lazy とか

再帰はとくに実装がコンパクトになる

ただし、注意しないと非常に遅い実装になることもある

遅いときも下記の指針に従うことで、高速化できる。

更新が必要な内部状態を引数に持ってくるように引数を見直す

memoize を使って、計算を一度だけにする。

Page 17: フィボナッチ数列の作り方

1616

ご清聴ありがとうございました