Leo
Leo

Reputation: 2103

Why use enumerator in ruby

I stumbled upon such example:

file = "./path"
var = Enumerator.new do |y|
CSV.foreach(file) do |row|
            y.yield(row)
    end
end

Question is, why store data of any kind, inside enumerators, insted of arrays? what is the difference in behaviour between whats above and this:

file = "./path"
var = []
CSV.foreach(file) do |row|
    var << row
end

when i want to something with the data it looks the same for both cases:

var.each {|row| puts row}

So what are the advantages and disadvantages of such constructions?

Upvotes: 1

Views: 108

Answers (2)

Tal Avissar
Tal Avissar

Reputation: 10304

In general, iterators can be used to lazily generate a sequence of objects. This is the main advantage when creating a lazy enumeration compared to creating a collection of items which is much more efficient.

For example, if your enumerator loop iterates over just the first 5 items of 3 million items then that's all yield returns, and you didn't build up a collection of 1 million items internally first.

So, you do not need to load all the 3 millions items just for your callee function can continue and executes the rest of the code.

Iterators are means for returning sequences.

Sometimes The sequence might even be infinite.

It brings the functional programming concept of lazy evaluation to Ruby – at least for enumerations.

There is a huge difference between returning a collection and returning a collection generator.

Upvotes: 4

sschmeck
sschmeck

Reputation: 7675

Too see the difference of the code add a puts within the loops.

seq = (1..3)
enum = Enumerator.new do |y|
  seq.each do |i|
    puts "Grabbing #{i} with enumerator"
    y.yield(i)
  end
end
enum.each { |i| puts "Taken #{i} from enumerator" }

# Grabbing 1 with enumerator
# Taken 1 from enumerator   
# Grabbing 2 with enumerator
# Taken 2 from enumerator   
# Grabbing 3 with enumerator
# Taken 3 from enumerator        

array = []
seq.each do |i|
  puts "Grabbing #{i} with array"
  array << i
end
array.each { |i| puts "Taken #{i} from array" }

# Grabbing 1 with array     
# Grabbing 2 with array     
# Grabbing 3 with array     
# Taken 1 from array        
# Taken 2 from array        
# Taken 3 from array   

Like mentioned by Tal Avissar the Enumerator fetches the value when it's needed which is called lazy evaluation. This behavoir is a benefit in some situations.

# infinite number sequence
numbers = Enumerator.new do |y|  
  n = 0                          
  loop { y.yield(n += 1) }       
end                              
puts numbers.take(3).join(', ')  
# 1, 2, 3

When dealing with huge or infinite sequences the difference is quite important.

Upvotes: 0

Related Questions