Enumerator: Ruby’s Versatile Iterator

Posted on by in Everything Else

The classic iterator pattern describes a way of accessing the elements of an aggregate object without exposing its implementation. This pattern comes in two flavors: external and internal. An external iterator is controlled by the client, while an internal iterator is controlled by the aggregate object.

In Ruby, internal iteration is the norm. Ruby’s Enumerable module adds several traversal, searching, and sorting algorithms to the main collection classes, Array and Hash. While working with Enumerable you may have come across Enumerator, Enumerable‘s close relative.

Enumerator is an Enumerable plus external iteration. In this post, we’ll take a look at the basics of Enumerators and some of the powerful functionality that they make possible.

Creating an Enumerator from a Block

One way to create an Enumerator is by passing a block to its constructor. A yielder object will be passed to this block. The yielder’s #<< method can be used to define the elements the Enumerator will iterate through. Enumerator#next can then be used to provide a controlled, external iteration.

Each time Enumerator#next is called, the Enumerator will proceed to the next yielded value. It will
then yield the value, giving control back to the caller. On the subsequent call to Enumerator#next, the Enumerator will resume execution up to the next yielded value. That's how an Enumerator maintains its current position in the iteration. After the last yielded value, a StopIteration error will be raised. Interestingly, StopIteration is rescued by Kernel#loop.

Creating an Enumerator from an Enumerable

The more common way to create an Enumerator is from an Enumerable object, specifically an object that defines an #each method. Object#to_enum (aliased to Object#enum_for) is implemented to return a new Enumerator that will enumerate by sending #each to its receiver.

Enumerator itself is Enumerable, so we can use the full power of Enumerable.

Object#to_enum takes a method name to generate the Enumerator. The default is #each. Passing in an alternative method can result in some strange behavior.

In this example, we created an Enumerator based on Array#map. This causes Enumerator#each to act like #map. Enumerator#with_index was then used to create a #map_with_index method.

Creating an Enumerator from a Blockless Enumerable Method Call

Several Enumerable methods that take a block will return an Enumerator when called without a block. For example, calling Array#select without a block will return an Enumerator with an #each method that will filter like #select. These blockless calls offer a concise alternative to Object#to_enum.

Turning Non-Enumerables into Enumerables

So far, we've looked at how to construct Enumerators from scratch and from Enumerables. However, several non-Enumerable classes also return Enumerators. Since Enumerator is Enumerable, this allows you to effectively turn a non-Enumerable class into an Enumerable one.

Integer, for example, returns an Enumerator from #times, #upto, and #downto.

String has several iteration methods that will return an Enumerator when not given a block.

Infinite Lists

An Enumerator's external iteration can be used to construct infinite lists.

An Enumerator will yield one value at a time. It pauses after each yield. When asked for another value, it will resume immediately after the last yielded value, and execute up to the next yielded value. In the above example, we used Kernel#loop to prevent the enumeration from ever stopping.

Lazy Enumeration

One problem with infinite lists is that an internal iteration method, for example #select, will hang forever. This is because methods like #select and #map rely on filtering/transforming the entire collection, i.e., they'll ask for the entire infinite list.

Continuing with our above infinite list example:

Lazy versions of iternal iteration methods provide a solution.

Enumerator#lazy_select returns a new Enumerator that will yield each of its values that the given block returns true for. Enumerable#first is necessary to limit the number of results.

Enumerators Everywhere

Enumerators seem to be everywhere in Ruby's standard library. External iteration, infinite lists, and lazy iteration, while interesting, are probably not something you'll ever use. However, an Enumerator's ability to turn non-Enumerables into Enumerables can result in some pretty elegant code. Ruby 1.9 put Enumerators on center stage by integrating them tightly with Enumerable. Since Enumerable is integral to just about every Ruby program, it's important to know what's possible with its close relative Enumerator.


  Comments: 14

  1. Michael Wynholds

    Very interesting post Jared. I haven’t full utilized all the power of enumerators in my Ruby projects in the past. It seems that using them skillfully can result in some very elegant code. I especially like the idea of the lazy enumerators for doing interesting “streaming” of data out of large data sources. I definitely like the power that Enumerators and Iterators gave in Java, but with blocks, Ruby opens up many more possibilities for elegant code.

    One small code issue:

    Gist 4.rb
    enum.each {|_, value| value * 10}_ # [10, 20]

    I think that line should not have an underscore at the end.

  2. Marc-André Lafortune

    An easier infinite enumerator: `(0..Float::INFINITY).each`

  3. Insightful post Jared. Did you do this to improve code clarity when chaining methods on Enumerable (i.e. creating appropriately named local variables for returned Enumerators)?

  4. Innokenty Mikhailov

    Nice article.
    You can read more about Enumerator::Lazy that will be available in Ruby 2.0 here

  5. enum.grep /[aeiou]/ # [“a”, “e”, “i”]

    should be

    enum.grep /[aeiou]/ # [“a”, “e”]

  6. 高品質2015シャネル スーパーコピー激安專門店弊社は海外大好評を博くシャネル コピー激安老舗です,2015高品質シャネル バッグ コピー,シャネル 靴 コピー,シャネル 財布 コピー品の品質は

    高品質2015シャネル スーパーコピー激安專門店弊社は海外大好評を博くシャネル コピー激安老舗です,2015高品質シャネル バッグ コピー,シャネル 靴 コピー,シャネル 財布 コピー品の品質はよくて、激安の大特価でご提供します。 http://www.gginza.com/bag/chanel/index.html

  7. apartmani zlatibor


  8. najstariji beogradski portal


  9. 当店は信頼できるルイヴィトンスーパーコピーN級品販売店。100%実物写真ですし、品質が完璧です。高品質ルイヴィトンコピー,安全税関対策+素早い配送+随時の在庫補給+丁寧な対応+スーパ

    当店は信頼できるルイヴィトンスーパーコピーN級品販売店。100%実物写真ですし、品質が完璧です。高品質ルイヴィトンコピー,安全税関対策+素早い配送+随時の在庫補給+丁寧な対応+スーパーコピー販売業界最低価格に挑戦!ルイヴィトンスーパーコピー http://www.newkakaku.net/guz1.htm

  10. We loved this, We will use this in future.

    Thanks for the share.

Your feedback