【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でまわしていくイメージ。