2

Michael Harrisonは、Rubyの怠惰な列挙子lazy_selectに関するすばらしい投稿をしており、との実装を提供していlazy_mapます。次のの実装には、タイプlazy_flatten以外の特別な処理が必要かどうか疑問に思っています。EnumeratorEnumerable

class Enumerator

  def lazy_flatten
    Enumerator.new do |yielder|
      self.each do |value|
        if value.kind_of? Enumerator
          value.lazy_flatten.each do |v|
            yielder.yield v
          end
        elsif value.kind_of? Enumerable
          value.flatten.each do |v|
            yielder.yield v
          end
        else
          yielder.yield value
        end
      end
    end
  end

end
4

2 に答える 2

4
  1. flattenあなたはまだ古い(怠惰ではない)下で演奏しているので、これは私には怠惰に見えません。
  2. EnumeratorですEnumerableので、別に扱う必要はないと思います。
  3. lazy_flatten私はのメソッドになることを期待しますEnumerable

これが私がそれを実装する方法です:

module Enumerable
  def lazy_flatten
    Enumerator.new do |yielder|
      each do |element|
        if element.is_a? Enumerable
          element.lazy_flatten.each do |e|
            yielder.yield(e)
          end
        else
          yielder.yield(element)
        end
      end
    end
  end
end
于 2012-04-09T12:14:18.267 に答える
1

Ruby 2.0以降では、これを行う必要はなく、を使用するだけEnumerable#lazyで、を返すことに注意してくださいEnumerator::Lazy

理由はわかりLazyませんが、はありませんがflatten、ありますので、原則として恒等関数flat_mapで使用できます。flat_map

module Enumerable
  def lazy_flatten
    self.lazy.flat_map { |x| x }
  end
end

Lazy#flat_map ほとんどの場合、分解可能な要素の分解を処理しますが、完全ではありません-ドキュメントから:

次の条件のいずれかが当てはまる場合、ブロックによって返される値xは分解されます。

  1. xeachはとの両方に応答します。これは、怠惰な列挙子forceであることを意味します。x
  2. xは配列であるか、に応答しto_aryます。

おそらく、無限シーケンスから配列への暗黙の変換を思いとどまらせるto_aryための方法ではないことに注意してください。これは、たとえば、上記のコードでaまたはaを含むものを実行しようとすると、それ(arguaby、以下を参照)が機能しないことを意味します。Enumerablelazy_flattenSetRange

a = [[1, 2, 3], Set[4, 5], 6, 7..8]
# => [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8] 
f = a.lazy_flatten
# => #<Enumerator::Lazy: #<Enumerator::Lazy: [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8]>:flat_map>  
f.to_a
# => [1, 2, 3, #<Set: {4, 5}>, 6, 7..8]

Array#flattenただし、これは:の動作と同じです。

a.flatten
# => [1, 2, 3, #<Set: {4, 5}>, 6, 7..8]

(ただしArray#flatten、怠惰な列挙子を検出して分解することはありませんLazy#flat_map。)

一方、OPのコードまたはMladenJablanovićの回答Setのコードは、およびRange:を分解します。

f = a.lazy_flatten # (M.J.'s code)
# => #<Enumerator: #<Enumerator::Generator:0x00007fd819c166c0>:each>
f.to_a
# => [1, 2, 3, 4, 5, 6, 7, 8]

ただし、そのコードは、無限のシーケンスを含むものが渡された場合にも無限に繰り返されます。

a = [[1, 2, 3], Set[4, 5], 6, 7..8, 9..Float::INFINITY]
# => [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8, 9..Infinity]
f = a.lazy_flatten # (M.J.'s code)
# => #<Enumerator: #<Enumerator::Generator:0x00007fd819a73d18>:each>
f.to_a
# => spins at 100% CPU for a while and eventually runs out of memory

バグではなく機能を検討する場合、1つのアプローチは、flat_mapベースの実装を変更して、見つかった列挙可能オブジェクトを遅延したものに変換することです。

module Enumerable
  def lazy_flatten
    self.lazy.flat_map do |x|
      x.respond_to?(:lazy) ? x.lazy : x
    end
  end
end

これは、ネストされた遅延列挙型に対しても機能し、Lazy#lazyそれ自体を返すのに十分スマートです。

于 2021-01-25T21:32:17.583 に答える