简体   繁体   English

Ruby 基于枚举器的惰性展平方法

[英]Ruby Enumerator-based lazy flatten method

Michael Harrison has a great post on lazy enumerators in Ruby , providing an implementation of lazy_select and lazy_map .迈克尔·哈里森 (Michael Harrison) 在 Ruby 中发表了一篇关于惰性枚举器的精彩文章,提供了lazy_selectlazy_map的实现。 I am wondering whether the following implementation of lazy_flatten should have special processing for anything other than Enumerator and Enumerable types.我想知道下面的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
  1. This doesn't seem lazy to me, as you are still performing old (non-lazy) flatten beneath.这对我来说似乎并不懒惰,因为您仍在执行旧的(非懒惰的) flatten
  2. Enumerator is Enumerable , so I think you don't need to handle it separately. EnumeratorEnumerable ,所以我认为你不需要单独处理它。
  3. I would expect lazy_flatten to be method on Enumerable .我希望lazy_flatten成为Enumerable上的方法。

Here's how I would implement it:下面是我将如何实现它:

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

Note that in Ruby 2.0+, you don't need to do this, you can just use Enumerable#lazy , which returns an Enumerator::Lazy .请注意,在 Ruby 2.0+ 中,您不需要执行此操作,您可以只使用Enumerable#lazy ,它返回一个Enumerator::Lazy

For reasons that aren't clear to me, Lazy doesn't have flatten , but it has flat_map , so in principle you could just use flat_map with the identity function .由于我不清楚的原因, Lazy没有flatten ,但它有flat_map ,所以原则上你可以使用身份为flat_mapflat_map

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

Lazy#flat_map mostly takes care of decomposing any decomposable elements, but not quite -- from the docs : Lazy#flat_map主要负责分解任何可分解的元素,但不完全是——来自文档

A value x returned by block is decomposed if either of the following conditions is true:如果以下任一条件为真,则块返回的值x将被分解:

  1. x responds to both each and force , which means that x is a lazy enumerator. x响应eachforce ,这意味着x是惰性枚举器。
  2. x is an array or responds to to_ary . x是一个数组或响应to_ary

Note that to_ary is not a method on Enumerable , presumably to discourage implicit conversions from infinite sequences to arrays. This means, for instance, that if you try to lazy_flatten something that contains a Set or a Range with the above code, it (arguaby, see below) won't work:请注意, to_ary不是Enumerable上的方法,大概是为了阻止从无限序列到 arrays 的隐式转换。这意味着,例如,如果您尝试使用上述代码对包含SetRange的内容进行lazy_flatten ,它(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]

However, this is the same as the behavior of Array#flatten :但是,这与Array#flatten的行为相同:

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

(Although Array#flatten will not detect and decompose lazy enumerators, and Lazy#flat_map will.) (尽管Array#flatten不会检测和分解惰性枚举器,而Lazy#flat_map会。)

Whereas the OP's code or the code in Mladen Jablanović's answer will decompose the Set and the Range :而 OP 的代码或Mladen Jablanović 的答案中的代码将分解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]

That code, however, will also iterate infinitely if passed something that includes an infinite sequence:但是,如果传递包含无限序列的内容,该代码也将无限迭代:

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

If you consider that a feature, rather than a bug, one approach would be to modify the flat_map -based implementation to convert any enumerables it finds to lazy ones:如果您认为这是一个功能,而不是一个错误,一种方法是修改基于flat_map的实现,将它找到的任何可枚举转换为惰性可枚举:

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

This works even for nested lazy enumerables, as Lazy#lazy is smart enough to return itself.这甚至适用于嵌套的惰性枚举,因为Lazy#lazy足够聪明,可以返回自身。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM