Search code examples
rubyenumerationenumerator

How to use an enumerator


In the Ruby Array Class documentation, I often find:

If no block is given, an enumerator is returned instead.

Why would I not pass a block to #map? What would be the use of my doing just:

[1,2,3,4].map

instead of doing:

[1,2,3,4].map{ |e| e * 10 } # => [10, 20, 30, 40]

Can someone show me a very practical example of using this enumerator?


Solution

  • The main distinction between an Enumerator and most other data structures in the Ruby core library (Array, Hash) and standard library (Set, SortedSet) is that an Enumerator can be infinite. You cannot have an Array of all even numbers or a stream of zeroes or all prime numbers, but you can definitely have such an Enumerator:

    evens = Enumerator.new do |y|
      i = -2
      y << i += 2 while true
    end
    
    evens.take(10)
    # => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    
    zeroes = [0].cycle
    
    zeroes.take(10)
    # => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    

    So, what can you do with such an Enumerator? Well, three things, basically.

    1. Enumerator mixes in Enumerable. Therefore, you can use all Enumerable methods such as map, inject, all?, any?, none?, select, reject and so forth. Just be aware that an Enumerator may be infinite whereas map returns an Array, so trying to map an infinite Enumerator may create an infinitely large Array and take an infinite amount of time.

    2. There are wrapping methods which somehow "enrich" an Enumerator and return a new Enumerator. For example, Enumerator#with_index adds a "loop counter" to the block and Enumerator#with_object adds a memo object.

    3. You can use an Enumerator just like you would use it in other languages for external iteration by using the Enumerator#next method which will give you either the next value (and move the Enumerator forward) or raise a StopIteration exception if the Enumerator is finite and you have reached the end.

    Eg., an infinite range: (1..1.0/0)