class Enumerator

一个允许内部和外部迭代的类。

可以通过以下方法创建 Enumerator

大多数方法都有两种形式:一种是块形式,其中内容为枚举中的每个项目进行求值;另一种是非块形式,它返回一个新的 Enumerator 来包装迭代。

enumerator = %w(one two three).each
puts enumerator.class # => Enumerator

enumerator.each_with_object("foo") do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

enum_with_obj = enumerator.each_with_object("foo")
puts enum_with_obj.class # => Enumerator

enum_with_obj.each do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

这允许您将 Enumerators 链接在一起。例如,您可以通过以下方式将列表的元素映射为包含索引和元素作为字符串的字符串:

puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]

外部迭代

Enumerator 也可以用作外部迭代器。例如,Enumerator#next 返回迭代器的下一个值,如果 Enumerator 结束,则引发 StopIteration

e = [1,2,3].each   # returns an enumerator object.
puts e.next   # => 1
puts e.next   # => 2
puts e.next   # => 3
puts e.next   # raises StopIteration

nextnext_valuespeekpeek_values 是唯一使用外部迭代的方法(以及内部使用 nextArray#zip(Enumerable-not-Array))。

这些方法不会影响其他内部枚举方法,除非底层迭代方法本身具有副作用,例如 IO#each_line

如果这些方法针对冻结的枚举器调用,将引发 FrozenError。由于 rewindfeed 也改变了外部迭代的状态,因此这些方法也可能引发 FrozenError

外部迭代因使用 Fiber 而与内部迭代**显著**不同

具体来说

Thread.current[:fiber_local] = 1
Fiber[:storage_var] = 1
e = Enumerator.new do |y|
  p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
  p Fiber[:storage_var] # => 1, inherited
  Fiber[:storage_var] += 1
  y << 42
end

p e.next # => 42
p Fiber[:storage_var] # => 1 (it ran in a different Fiber)

e.each { p _1 }
p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)

将外部迭代转换为内部迭代

您可以使用外部迭代器来实现内部迭代器,如下所示:

def ext_each(e)
  while true
    begin
      vs = e.next_values
    rescue StopIteration
      return $!.result
    end
    y = yield(*vs)
    e.feed y
  end
end

o = Object.new

def o.each
  puts yield
  puts yield(1)
  puts yield(1, 2)
  3
end

# use o.each as an internal iterator directly.
puts o.each {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

# convert o.each to an external iterator for
# implementing an internal iterator.
puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3