【问题标题】:Ruby Enumerator-based lazy flatten method基于 Ruby Enumerator 的惰性扁平化方法
【发布时间】:2012-04-09 05:04:47
【问题描述】:

Michael Harrison 在lazy enumerators in Ruby 上有一篇很棒的帖子,提供了lazy_selectlazy_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

【问题讨论】:

    标签: ruby-on-rails ruby functional-programming lazy-evaluation enumerator


    【解决方案1】:
    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
    

    【讨论】:

      【解决方案2】:

      请注意,在 Ruby 2.0+ 中,您不需要这样做,只需使用 Enumerable#lazy,它会返回一个 Enumerator::Lazy

      由于我不清楚的原因,Lazy 没有flatten,但它有flat_map,因此原则上您可以将flat_mapidentity function 一起使用。

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

      Lazy#flat_map 大部分负责分解任何可分解的元素,但并不完全——来自docs

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

      1. x 同时响应eachforce,这意味着x 是一个惰性枚举器。
      2. x 是一个数组或响应to_ary

      注意to_ary 不是Enumerable 上的方法,大概是为了阻止implicit conversions 从无限序列到数组。这意味着,例如,如果您尝试使用上述代码 lazy_flatten 包含 SetRange 的内容,它(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 Jablanović's answer 中的代码将分解 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 足够聪明,可以返回自己。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-26
        • 2019-10-15
        • 2011-11-03
        • 1970-01-01
        • 2016-09-13
        相关资源
        最近更新 更多