Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of the Enumerator class in Ruby

If I create an Enumertor like so:

enum = [1,2,3].each => #<Enumerator: [1, 2, 3]:each> 

enum is an Enumerator. What is the purpose of this object? I can't say this:

enum { |i| puts i }

But I can say this:

enum.each { |i| puts i }

That seems redundant, because the Enumerator was created with .each. It seems like it's storing some data regarding the each method.

I don't understand what's going on here. I'm sure there is some logical reason we have this Enumerator class, but what can it do that an Array can't? I thought maybe it was an ancestor of Array and other Enumerables, but it doesn't seem to be. What exactly is the reason for the existence of the Enumerator class, and in what context would it ever be used?

like image 598
ordinary Avatar asked Jun 06 '13 23:06

ordinary


3 Answers

What happens if you do enum = [1,2,3].each; enum.next?:

enum = [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>
enum.next
=> 1
enum.next
=> 2
enum.next
=> 3
enum.next
StopIteration: iteration reached an end

This can be useful when you have an Enumerator that does a calculation, such as a prime-number calculator, or a Fibonacci-sequence generator. It provides flexibility in how you write your code.

like image 74
the Tin Man Avatar answered Nov 14 '22 00:11

the Tin Man


I think, the main purpose is to get elements by demand instead of getting them all in a single loop. I mean something like this:

e = [1, 2, 3].each
... do stuff ...
first = e.next
... do stuff with first ...
second = e.next
... do more stuff with second ...

Note that those do stuff parts can be in different functions far far away from each other.

Lazily evaluated infinite sequences (e.g. primes, Fibonacci numbers, string keys like 'a'..'z','aa'..'az','ba'..'zz','aaa'.. etc.) are a good use case for enumerators.

like image 37
Sergey Bolgov Avatar answered Nov 14 '22 01:11

Sergey Bolgov


As answered so far, Enumerator comes in handy when you want to iterate through a sequence of data of potentially infinite length.

Take a prime number generator prime_generator that extends Enumerator for example. If we want to get the first 5 primes, we can simply write prime_generator.take 5 instead of embedding the "limit" into the generating logic. Thus we can separate generating prime numbers and taking a certain amount out of generated prime numbers making the generator reusable.

I for one like method chaining using methods of Enumerable returning Enumerator like the following example (it may not be a "purpose" but I want to just point out an aesthetic aspect of it):

prime_generator.take_while{|p| p < n}.each_cons(2).find_all{|pair| pair[1] - pair[0] == 2}

Here the prime_generator is an instance of Enumerator that returns primes one by one. We can take prime numbers below n using take_while method of Enumerable. The methods each_cons and find_all both return Enumerator so they can be chained. This example is meant to generate twin primes below n. This may not be an efficient implementation but is easily written within a line and IMHO suitable for prototyping.

Here is a pretty straightforward implementation of prime_generator based on Enumerator:

def prime?(n)
  n == 2 or
    (n >= 3 and n.odd? and (3...n).step(2).all?{|k| n%k != 0})
end
prime_generator = Enumerator.new do |yielder|
  n = 1
  while true
    yielder << n if prime? n
    n += 1
  end
end
like image 23
M. Shiina Avatar answered Nov 14 '22 01:11

M. Shiina