フィボナッチ数列の作り方
2013/2/2
cuzic
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
自己紹介
最近のできごと
Dell XPS 12 を買いました。
不思議なギミックでタブレットになる
Full HD、Touch Panel、Display Portポー...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
22フィボナッチ数列について
次の漸化式を満たす数列をフィボナッチ数列という
フィボナッチ数列の特徴
うさぎの増え方を表現する漸化式
𝐹𝑛−1
𝐹𝑛
が 黄金比(
5−1...
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) もっとも単...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
ブロック付メソッド 44
■ (4) ブロック付メソッド呼び出し ■ 処理の実施順
yield キーワードにより、ブロック引数に値を渡せる。
ブロック付メソッド呼出しによ...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クラスの利用 55
■ (5) クラスの利用
クラスを利用し、内部状態をインスタンス変数に持たせる方法もある
この場合は、next を実行したときに内部状態が変化する。
...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
クロージャ 66
■ (6) クロージャの利用
クロージャという機能により、ブロックの中から
ネストの外側の変数を参照できる。
内部状態を持つ関数を簡易に作ることができる...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Ruby 1.8: Generator の利用 77
■ (7) Generator の利用
Ruby 1.8 には、標準ライブラリに Generator があった
内部...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Ruby 1.9: Enumerator の利用 88
■ (8) Enumerator の利用
Ruby 1.9 では Enumerator を利用可能
内部実装に F...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考) Enumerator と Fiber 99
e = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b ...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerator はベンリ 1010
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b,...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
Enumerable#lazy の利用 1111
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b ...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
(参考)Enumerable#lazy の実装(Ruby 2.0 での実際の実装とは異なる) 1212
module Enumerable
def lazy
Enumer...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
単純な再帰と末尾再帰
フィボナッチ数列を再帰で求めてみる
関数型プログラミング言語の入門には、ほぼ必ず登場する例
適度な難易度で説明しやすい
末尾再帰による高速化、遅延評...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
再帰と memoize
memoize という高速化手法がある
最初の一度だけ計算し、2回目以降は前回計算済みの値を返す
メモリを多く消費するが、計算時間を短縮できる
同...
2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」
1515まとめ
単純な問題だけど、いろんな解法がある
ループ、ブロック付メソッド、クラス、クロージャ、
Enumerator、再帰
個人的オススメは、Enumerator...
1616
ご清聴ありがとう
ございました
Upcoming SlideShare
Loading in …5
×

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

3,890 views

Published on

Published in: Technology
  • Be the first to comment

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

  1. 1. フィボナッチ数列の作り方 2013/2/2 cuzic
  2. 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
  3. 3. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」 22フィボナッチ数列について 次の漸化式を満たす数列をフィボナッチ数列という フィボナッチ数列の特徴 うさぎの増え方を表現する漸化式 𝐹𝑛−1 𝐹𝑛 が 黄金比( 5−1 2 = 0.683 …) に収束する プログラミングにおけるフィボナッチ数列 関数型プログラミング言語における Hello, World! 入門向けに使える程度の複雑さ 切り口によって、いろんな説明ができる 今回は、フィボナッチ数列の求め方をいろいろ説明します。 𝑭 𝒏 = 𝟎 𝟏 𝑭 𝒏−𝟏 + 𝑭 𝒏−𝟐 𝒏 = 𝟎 𝒏 = 𝟏 𝒏 ≥ 𝟐
  4. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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 する例
  16. 16. 2013/2/2 第56回Ruby勉強会 「フィボナッチ数列のつくり方」 1515まとめ 単純な問題だけど、いろんな解法がある ループ、ブロック付メソッド、クラス、クロージャ、 Enumerator、再帰 個人的オススメは、Enumerator による実装 利用側のコードがコンパクト Enumerable モジュールの便利メソッドが使える Enumerable#lazy とか 再帰はとくに実装がコンパクトになる ただし、注意しないと非常に遅い実装になることもある 遅いときも下記の指針に従うことで、高速化できる。 更新が必要な内部状態を引数に持ってくるように引数を見直す memoize を使って、計算を一度だけにする。
  17. 17. 1616 ご清聴ありがとう ございました

×