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. EnumeratorEnumerable,所以我认为你不需要单独处理它。
  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 主要负责分解任何可分解的元素,但不完全——来自文档

如果以下任一条件为真,则 block 返回的值x被分解:

  1. xeachand都有响应force,这意味着它x是一个惰性枚举器。
  2. x是一个数组或响应to_ary

请注意,这to_ary不是 上的方法Enumerable,大概是为了阻止从无限序列到数组的隐式转换。这意味着,例如,如果您尝试使用上述代码lazy_flatten包含 aSet或 aRange的内容,它(arguaby,见下文)将不起作用:

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 Jabnović 的答案中的代码将分解SetRange

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它足够聪明以返回自身。

于 2021-01-25T21:32:17.583 回答