On this page
Enumerator
A class which allows both internal and external iteration.
An Enumerator can be created by the following methods.
Most methods have two forms: a block form where the contents are evaluated for each item in the enumeration, and a non-block form which returns a new Enumerator wrapping the iteration.
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
This allows you to chain Enumerators together. For example, you can map a list’s elements to strings containing the index and the element as a string via:
puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } # => ["0:foo", "1:bar", "2:baz"]
External Iteration
An Enumerator can also be used as an external iterator. For example, Enumerator#next returns the next value of the iterator or raises StopIteration if the Enumerator is at the end.
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
next, next_values, peek, and peek_values are the only methods which use external iteration (and Array#zip(Enumerable-not-Array) which uses next internally).
These methods do not affect other internal enumeration methods, unless the underlying iteration method itself has side-effect, e.g. IO#each_line.
FrozenError will be raised if these methods are called against a frozen enumerator. Since rewind and feed also change state for external iteration, these methods may raise FrozenError too.
External iteration differs significantly from internal iteration due to using a Fiber:
-
The
Fiberadds some overhead compared to internal enumeration. -
The stacktrace will only include the stack from the
Enumerator, not above. -
Fiber-local variables are not inherited inside the
EnumeratorFiber, which instead starts with no Fiber-local variables. -
Fiberstorage variables are inherited and are designed to handleEnumeratorFibers. Assigning to aFiberstorage variable only affects the currentFiber, so if you want to change state in the callerFiberof theEnumeratorFiber, you need to use an extra indirection (e.g., use some object in theFiberstorage variable and mutate some ivar of it).
Concretely:
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)
Convert External Iteration to Internal Iteration
You can use an external iterator to implement an internal iterator as follows:
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
Class methods 3
Instance methods 14
Ruby version changes 2
-
4.0
-
Enumerator.producenow accepts an optionalsizekeyword argument to specify the size of the enumerator. It can be an integer,Float::INFINITY, a callable object (such as a lambda), ornilto indicate unknown size. When not specified, the size defaults toFloat::INFINITY.Example# Infinite enumerator enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) enum.size # => Float::INFINITY # Finite enumerator with known/computable size abs_dir = File.expand_path("./baz") # => "/foo/bar/baz" traverser = Enumerator.produce(abs_dir, size: -> { abs_dir.count("/") + 1 }) { raise StopIteration if it == "/" File.dirname(it) } traverser.size # => 4
-
-
3.2
-
Enumerator.producthas been added.Enumerator::Productis the implementation. [Feature #18685]
-