せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】外部イテレーターを使って並列イテレーションを実現する

 
昨日は、浅草rubyの勉強会に出席して、すごい楽しかったよ。
詳しい内容は今日中に書きます。
宿題ももらったー。

 
この本を読んでいます。

プログラミング言語 Ruby

プログラミング言語 Ruby

この中で

外部イテレーターは並列イテレーションの問題を解決するのに有用

とあったので、コードを書いて確認してみる。
 
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でまわしていくイメージ。