Michael Harrison在lazy enumerators in Ruby上发布了一篇很棒的帖子,提供了lazy_select
和lazy_map
的实现。我想知道lazy_flatten
的以下实现是否应对除Enumerator
和Enumerable
类型之外的任何内容进行特殊处理。
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
答案 0 :(得分:3)
flatten
。Enumerator
是Enumerable
,因此我认为您无需单独处理。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
答案 1 :(得分:1)
请注意,在 Ruby 2.0+ 中,您不需要这样做,您只需使用 Enumerable#lazy
,它会返回一个 Enumerator::Lazy
。
由于我不清楚的原因,Lazy
没有 flatten
,但它有 flat_map
,因此原则上您可以将 flat_map
与identity function。
module Enumerable
def lazy_flatten
self.lazy.flat_map { |x| x }
end
end
Lazy#flat_map
主要负责分解任何可分解的元素,但不完全——来自docs:
如果满足以下任一条件,则块返回的值 x 将被分解:
each
和 force
,这意味着 x
是一个惰性枚举器。to_ary
。请注意,to_ary
不是 Enumerable
上的方法,大概是为了阻止 implicit conversions 从无限序列到数组。这意味着,例如,如果您尝试使用上述代码 lazy_flatten
包含 Set
或 Range
的内容,它(arguaby,见下文)将不起作用:< /p>
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 的代码或 Mladen Jablanović's answer 中的代码将分解 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
如果您认为这是一个功能,而不是一个错误,一种方法是修改基于 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
足够聪明,可以返回自身。