【问题标题】:Turning several Enumerables into one将几个 Enumerable 合二为一
【发布时间】:2019-02-07 01:15:07
【问题描述】:

有没有办法让多个 Enumerable 对象显示为单个 Enumerable 而不将其展平为数组?目前我已经写了一个这样的类,但我觉得必须有一个内置的解决方案。

class Enumerables
  include Enumerable

  def initialize
    @enums = []
  end

  def <<(enum)
    @enums << enum
  end

  def each(&block)
    if block_given?
      @enums.each { |enum|
        puts "Enumerating #{enum}"
        enum.each(&block)
      }
    else
      to_enum(:each)
    end
  end
end

enums = Enumerables.new
enums << 1.upto(3)
enums << 5.upto(8)
enums.each { |s| puts s }

作为一个简单的例子,它需要能够接受像这样的无限枚举器。

inf = Enumerator.new { |y| a = 1; loop { y << a; a +=1 } };

【问题讨论】:

  • 最后一行相当于inf = 1.step

标签: ruby enumerable


【解决方案1】:

嗯,它可以通过使用Enumerator 的标准库来完成。这种方法的优点是它返回 real 枚举器,它可能被映射、减少等。

MULTI_ENUM = lambda do |*input|
  # dup is needed here to prevent
  #  a mutation of inputs when given
  #  as a splatted param
  # (due to `input.shift` below)
  input = input.dup.map(&:to_enum)
  Enumerator.new do |yielder|
    loop do
      # check if the `next` is presented
      #  and mutate the input swiping out
      #  the first (already iterated) elem
      input.first.peek rescue input.shift
      # stop iteration if there is no input left
      raise StopIteration if input.empty?
      # extract the next element from 
      #  the currently iterated enum and
      #  append it to our new Enumerator
      yielder << input.first.next
    end
  end
end

MULTI_ENUM.(1..3, 4.upto(5), [6, 7]).
  map { |e| e ** 2 }

#⇒ [1, 4, 9, 16, 25, 36, 49]

【讨论】:

  • 谢谢!我不确定我是否理解所有这些。 Enumerator::new 文档不清楚并且 Enumerator::Yielder 文档是空白的。你能用一些 cmets 注释它吗?另外,dup 有必要吗?
  • 已更新。基本上,Enumerator 是在没有更多元素时调用yield 或引发StopIteration 的东西。它落后于 to_enum 的内部实现。
  • 谢谢,我想我明白了。 input.dup 只是参数数组的一个浅表克隆,因此您可以 input.shift 而不是使用计数器。当当前枚举为空时,input.first.peek rescue input.shift 前进到下一个枚举(我之前没有看到 resuce 作为语句修饰符)。 raise StopIteration if input.empty? 在没有剩余枚举时结束该块。 yielder &lt;&lt; input.first.next 从当前枚举中获取下一个值并生成它。
  • 是的,完全正确。不过,我不确定这个解决方案是否比您的解决方案更好。老实说,我更喜欢嵌套enum 的课程和代表团。
【解决方案2】:

我最终得到了这个解决方案,可能与您已经尝试过的很接近:

def enumerate(*enum)
  enum.each_with_object([]) { |e, arr| arr << e.to_a }.flatten
end

enumerate( 1..3, 5.upto(8), 3.times, 'a'..'c' ).each { |e| p e }

# => 1, 2, 3, 5, 6, 7, 8, 0, 1, 2, "a", "b", "c"

或者(相同的机制):

def enumerate(*enum)
  enum.flat_map { |e| e.to_a }
end

【讨论】:

  • 感谢您的回答。使用arr &lt;&lt; e.to_a 意味着所有枚举的对象都变成了数组,所以它不适用于我的目的。例如,喂它inf = Enumerator.new { |y| a = 1; loop { y &lt;&lt; a; a +=1 } };,它会消耗所有内存。
  • 在此解决方案的框架内不需要展平,只需使用Array#concat
【解决方案3】:

毕竟。在元素上使用Enumerable::Lazy#flat_map.each.lazy

inf = Enumerator.new { |y| a = 1; loop { y << a; a += 1 } }
[(1..3).to_a, inf].lazy.flat_map { |e| e.each.lazy }.take(10).force
#⇒ [1, 2, 3, 1, 2, 3, 4, 5, 6, 7]

【讨论】:

  • 谢谢!正是我正在寻找的。我将把它包装成一个小函数。顺便说一句,我找不到force 的文档。我看到它只是to_a 的别名。
  • 我明白你的意思。它实际上只是to_a 的别名:rb_define_alias(rb_cLazy, "force", "to_a"); 我在Enumerator::Lazy#new 中找到了一些文档:“当枚举器实际被枚举(例如通过调用力)时,obj 将被枚举并且每个值传递给给定的块。”奇怪的疏忽,它不是一个记录的方法。
  • 懒惰?我必须研究它。似乎1..Float::INFINITY 也可以代替inf 工作。不是吗?
  • @iGian 和 1.step,是的。
猜你喜欢
  • 2016-04-27
  • 2022-06-27
  • 1970-01-01
  • 2013-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
  • 2015-12-30
相关资源
最近更新 更多