【Ruby】外部イテレーターを使って並列イテレーションを実現する
昨日は、浅草rubyの勉強会に出席して、すごい楽しかったよ。
詳しい内容は今日中に書きます。
宿題ももらったー。
この本を読んでいます。
- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (124件) を見る
この中で
とあったので、コードを書いて確認してみる。
Enumrator自体についてはこちら。
【Ruby】Enumeratorクラス その2。外部イテレータ、内部イテレータ。 - せかいや
問題
以下の出力を得る関数を実装せよ
a, b, c = [1,2,3], 4..6, 'a'..'e'
sequence(a, b, c){|x| print x}
interleave(a, b, c){|x| print x}
bundle(a, b, c){|x| print x}
■出力結果
123456abcde 14a25b36cde [1, 4, "a"][2, 5, "b"][3, 6, "c"]
実装したコード
def sequence(a, b, c) a.each{ |x| yield x } b.each{ |x| yield x } c.each{ |x| yield x } end def interleave(a, b, c) _max = [a.length, b.length, c.length] #Rangeオブジェクトはlengthメソッドを持たないのでエラー #eachを使う限りaの要素分しか繰り返せないので #この方法では実装できない a.each_with_index do |x, i| yield x b.each_with_index do |y, j| yield y if j==i c.each_with_index do |z, k| yield z if k==j && j==i end end end end def bundle(a, b, c) a.each_with_index do |x, i| b.each_with_index do |y, j| c.each_with_index do |z, k| yield [x, y, z] if k==j && j==i end end end end a, b, c = [1,2,3], 4..6, 'a'..'e' sequence(a, b, c){|x| print x} print "\n" interleave(a, b, c){|x| print x} print "\n" bundle(a, b, c){|x| print x}
なるほど。
2つは実装できたけど、
interleaveは、要素の長さ以上の繰り返しを実装する必要があるから、
eachでは実装できないと思う。
できる?
無理ではないかな。。?
ここで外部イテレーターの出番になる。
外からnextを使うと、好きな回数呼び出せる(ただしエラー発生に注意)。
オライリー本のコードを理解してみよう。
1こめ。
# オライリーのコード def sequence1(*enumrables, &block) enumrables.each do |enumrable| enumrable.each(&block) end end # 自分のコード def sequence(a, b, c) a.each{ |x| yield x } b.each{ |x| yield x } c.each{ |x| yield x } end
なるほど。
引数自体を可変長で持たせるのか。
こうやって書けば引数が増えてもコードの改修は必要ない。
2こめ。
def interleave1(*enumrables) enumrators = enumrables.map{|x| x.to_enum} while !enumrators.empty? begin e = enumrators.shift yield e.next rescue else enumrators << e end end end
なるほど。。
例外のelse節。。
3こめ。
自分で実装
def bundle1(*enumrables) enumrators = enumrables.map{|x| x.to_enum} accept = true while accept begin a = enumrators.map{|e| e.next} yield a rescue accept = false end end end
オライリー本ではloopを使う。
def bundle1(*enumrables) enumrators = enumrables.map{|x| x.to_enum} loop do a = enumrators.map{|e| e.next} yield a end end
StopIteration raised in the block breaks the loop.
http://www.ruby-doc.org/core-2.0.0/Kernel.html#method-i-loop
なるほどー。
loopは、フラグを使わなくても繰り返しを脱出できるのか。
whileはStopIterationが発生しても処理を抜けないようだ。
def bundle1(*enumrables) enumrators = enumrables.map{|x| x.to_enum} accept = true while accept a = enumrators.map{|e| e.next} yield a end end
[1, 4, "a"][2, 5, "b"][3, 6, "c"]C:/hoge1.rb:45:in `next': iteration reached an end (StopIteration)
なるほど。
並列、というのは、与えられたenumrableたちを1つのEnumratorとしてとらえて、
各要素をnextでまわしていくイメージ。