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は分解されます。
- x
each
はとの両方に応答します。これは、怠惰な列挙子force
であることを意味します。x
- xは配列であるか、に応答し
to_ary
ます。
おそらく、無限シーケンスから配列への暗黙の変換を思いとどまらせるto_ary
ための方法ではないことに注意してください。これは、たとえば、上記のコードでaまたはaを含むものを実行しようとすると、それ(arguaby、以下を参照)が機能しないことを意味します。Enumerable
lazy_flatten
Set
Range
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
それ自体を返すのに十分スマートです。