什么时候枚举器有用?

When is an enumerator useful?

一个Enumerator对象可以通过调用某些方法而不传递一个块来创建,例如Array#reject:

(1..3).reject.to_a
# => [1, 2, 3]

这只是 ruby 的一些规则以便于链接,还是有其他方法可以将行为传递给枚举器?

Is this just some rule of ruby for easier chaining

是的,正是这个原因。轻松链接枚举器。考虑这个例子:

ary = ['Alice', 'Bob', 'Eve']

records = ary.map.with_index do |item, idx|
  {
    id: idx,
    name: item,
  }
end

records # => [{:id=>0, :name=>"Alice"}, {:id=>1, :name=>"Bob"}, {:id=>2, :name=>"Eve"}]

map 将每个元素生成为 with_index,它在其顶部拍打项目索引并生成您的块。块 returns 值到 with_index,returns 到 map 哪个( 做它的事情,映射,和 )returns 给来电者。

Is this just some rule of ruby for easier chaining

这不是 Ruby 的规则。只是 reject 方法(例如)returns 和 Enumerator 如果在没有块的情况下调用。它可以做其他事情。

or is there some other way to pass behavior to the Enumerator?

是的,Enumerator 封装了您从中创建 Enumerator 的方法的行为。例如,从 reject 创建一个 Enumerator 会创建一个将拒绝其某些元素的对象。 Enumerator 混合在 Enumerable 中,因此可以用这个 Enumerator 做任何你可以用任何其他 Enumerable 做的事情。例如:

enum = (1..3).reject

enum.with_index {|el, i| i.even? }
# => [2]

正如@Sergio 所说,它主要用于链接,但不止于此。如果您有枚举器 e,则可以使用 Enumerator#next and Enumerator#peek 提取元素。以下是枚举器如何发挥优势的两个示例。

问题:给定一个数组a,构造另一个数组,其索引i处的值为a[i]如果i是奇数,如果 i 是偶数,则 2*a[i]。假设a = [1,2,3,4].

人们通常会看到:

a.map.with_index { |n,i| n.odd? ? n : 2*n } #=> [1,4,3,8]

但这也可以写成:

e = [1,2].cycle          #=> #<Enumerator: [1, 2]:cycle> 
a.map { |n| e.next * n } #=> [1, 4, 3, 8] 

问题: 给定一个数组 a,将相等的连续值分块到数组中。让我通过展示它通常是如何完成的来使这个陈述更准确。假设a = [1,1,2,3,3,3,4].

a.chunk(&:itself).map(&:last) #=> [[1, 1], [2], [3, 3, 3], [4]]

在 Ruby v2.2 中(其中 #itself made its debut), you could use Enumerable#slice_when:

a.slice_when { |f,l| f != l }.to_a
  #=> [[1, 1], [2], [3, 3, 3], [4]]

但您也可以使用枚举器:

e = a.to_enum
  #=> #<Enumerator: [1, 1, 2, 3, 3, 3, 4]:each> 
b = [[]]
loop do
  n = e.next
  b[-1] << n
  nxt = e.peek
  b << [] if nxt != n
end
b
  #=> [[1, 1], [2], [3, 3, 3], [4]]

请注意,当 ne 的最后一个值时,e.peek 将引发 StopInteration 异常。 Kernel#loop 通过跳出循环来处理该异常。

我并不是建议应优先使用最后一种方法而不是我提到的其他两种方法,但在其他情况下也可以有效地使用这种方法。

还有一件事:如果你有链式方法的表达式,你可以通过将枚举器转换为数组来检查其元素传递给块的枚举器的内容.从中您可以看到需要哪些块变量。假设你想写:

[1,2,3,4].each_with_index.with_object({}) {....}

并在块中做一些事情,但不确定如何表达块变量。你可以这样做:

e = [1,2,3,4].each_with_index.with_object({})
  #=> #<Enumerator: #<Enumerator: [1, 2, 3, 4]:each_with_index>
        :with_object({})> 
e.to_a
  #=> [[[1, 0], {}], [[2, 1], {}], [[3, 2], {}], [[4, 3], {}]] 

这表明(比方说)传递给块的 e 的第一个元素是:

[[1, 0], {}]

告诉使用块变量应该是:

(n,i), h = [[1, 0], {}]
n #=> 1 
i #=> 0 
h #=> {} 

表示表达式应该写成:

[1,2,3,4].each_with_index.with_object({}) { |(n,i),h|....}