Learning Ruby [enumerator, enumerable objects]

I did not provide a good description for iterators earlier, so let me do that now before jumping into the concept of enumerators. Iterators are methods that have the ability to iterate through a collection such as an array or hash table, and that yield one or more values to a code block.

Enumerators are objects that are created for doing iterations. These objects contain the standard suite of iterator methods available for all classes that mix-in the Enumerable module. To create an enumerator the logic for the each method needs to be defined. If you recall, the each logic is used to determine how all other iterator methods work in any class with the Enumerable module mixed-in.

Since enumerators are objects they are able to hold information about the current state of their iteration. That means you can step through an iteration little by little. Iterator methods are atomic; once the method call is completed they are not able to save information related to their iteration.

Enumerators can also be used to protect information. By transferring data from a collection to an enumerator object you can iterate through the data without the risk of it being changed.

There are three ways to create enumerators. The most common approach is to use an iterator method from an existing object to define the each logic for the newly minted Enumerator object. Most iterators methods return an enumerator object if they are called without a code block. As you can see in the example below, the each method when called without a code block returns a numerator, which I have assigned to a variable.

enum = (0..9).each
view raw gistfile1.rb This Gist brought to you by GitHub.

You can also call the enum_for method on a collection to create an enumerator. This method takes up to two arguments, the first specifies the iterator method that will be used as the basis of the each logic for new enumerator; the second is an optional initialization argument that is only useful for a limited number of methods, such as inject.

In the example below, the select iterator from ingredients collection is used as the basis of the each logic on this new enumerator object. This makes the each method on the new object function like the select method from the ingredients collection.

ingredients = ["tomato", "arugula", "garlic", "olive oil", "balsamic", "parmesan"]
enum = ingredients.enum_for(:select)
p enum.each { |n| n =~ /ar/ }
view raw gistfile1.rb This Gist brought to you by GitHub.

You can also use the Enumerator.new method to create an enumerator object. In this case you need to define the each logic for this object inside a block of code. Here you need to define the elements that this object will iterate through and the way in which this iteration will happen. This includes identifying the value that should be yielded by the iterator methods from this object.

In the example code below I have created an array of strings, called ingredients. It contains the elements that my enumerator object will iterate through. Then I use the each method from this array to define the way in which the iteration will happen, which is based on each element’s index in the array. Lastly, I use the expression yielder << ingredient to identify the value that should be yielded from this object on each iteration.

enum_ingredients = Enumerator.new do |yielder|
ingredients = ["tomato", "arugula", "garlic", "olive oil", "balsamic", "parmesan"]
ingredients.each { |ingredient| yielder << ingredient }
end
enum_ingredients.each {|n| puts n}
view raw gistfile1.rb This Gist brought to you by GitHub.

This last example is rather silly. I am sharing it to illustrate the point that you can create a new enumerator objects without using an existing iterator as the basis for your new object. The each logic defined below iterates through three different values, yielding each one in turn.

enum_silly = Enumerator.new do |yielder|
yielder << "rock"
yielder << "paper"
yielder << "scissors"
end
enum_silly.each {|n| puts n}
view raw gistfile1.rb This Gist brought to you by GitHub.

Leave a Reply