How to use an enumerator

In the documentation for the Ruby Array class, I often find:

If a block is not specified, a counter is returned instead.

Why don't I pass the block to #map ? What will be the use of my action:

 [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 counter?

+7
ruby enumeration enumerator
source share
6 answers

The main difference between Enumerator and most โ€  other data structures in the main Ruby library ( Array , Hash ) and the standard library ( Set , SortedSet ) is that the Enumerator can be infinite. You cannot have an Array all even numbers or a stream of zeros or all primes, but you can definitely have an Enumerator like this:

 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.

  • Enumerator is blended into Enumerable . So you can use all Enumerable methods like map , inject , all? , any? , none? , select , reject , etc. Just be aware that an Enumerator can be infinite, while map returns an Array , so when you try to map infinite Enumerator can create infinitely large Array and take an infinite amount of time.

  • There are transfer methods that somehow enrich the 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.

  • You can use the Enumerator same way you use it in other languages โ€‹โ€‹for external iteration 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 course, and you have reached the end.

โ€  For example, an infinite range: (1..1.0/0)

+4
source share

Good question.

What if we want to do several things with the created counter? We do not want to process it now because it means that we may need to create another later?

 my_enum = %w[now is the time for all good elves to get to work].map # => #<Enumerator: ["now", "is", "the", "time", "for", "all", "good", "elves", "to", "get", "to", "work"]:map> my_enum.each(&:upcase) # => ["NOW", "IS", "THE", "TIME", "FOR", "ALL", "GOOD", "ELVES", "TO", "GET", "TO", "WORK"] my_enum.each(&:capitalize) # => ["Now", "Is", "The", "Time", "For", "All", "Good", "Elves", "To", "Get", "To", "Work"] 
+7
source share

The return function is enumerable when a block is not specified, mainly used when combining functions from an enumerated class. Like this:

 abc = %w[abc] p abc.map.with_index{|item, index| [item, index]} #=> [["a", 0], ["b", 1], ["c", 2]] 

edit:

I think that my own understanding of this behavior is too limited to give a correct understanding of the inner workings of Ruby. I think the most important thing to note is that the arguments are passed in the same way as for Procs. That way, if the array is passed, it will be automatically "splatted" (best word for this?). I think the best way to get an understanding is to simply use some simple functions that return a list and start experimenting.

 abc = %w[abcd] p abc.each_slice(2) #<Enumerator: ["a", "b", "c", "d"]:each_slice(2)> p abc.each_slice(2).to_a #=> [["a", "b"], ["c", "d"]] p abc.each_slice(2).map{|x| x} #=> [["a", "b"], ["c", "d"]] p abc.each_slice(2).map{|x,y| x+y} #=> ["ab", "cd"] p abc.each_slice(2).map{|x,| x} #=> ["a", "c"] # rest of arguments discarded because of comma. p abc.each_slice(2).map.with_index{|array, index| [array, index]} #=> [[["a", "b"], 0], [["c", "d"], 1]] p abc.each_slice(2).map.with_index{|(x,y), index| [x,y, index]} #=> [["a", "b", 0], ["c", "d", 1]] 
+4
source share

In addition to hirolau's answer, there is another lazy method that you can combine to change the counter.

 a_very_long_array.map.lazy 

If map always accepts a block, then there must be another method, for example map_lazy , and in the same way for other iterators do the same.

+2
source share

Enumerator A class that allows both internal and external iteration.

 => array = [1,2,3,4,5] => array.map => #<Enumerator: [2, 4, 6, 8, 10]:map> => array.map.next => 2 => array.map.next_values => [0] 2 
0
source share

I assume that sometimes you want to pass the Enumerator another method, even though this Enumerator came from: map , slice , whatever:

 def report enum if Enumerator === enum enum.each { |e| puts "Element: #{e}" } else raise "report method requires enumerator as parameter" end end > report %w[one two three].map # Element: one # Element: two # Element: three > report (1..10).each_slice(2) # Element: [1, 2] # Element: [3, 4] # Element: [5, 6] # Element: [7, 8] # Element: [9, 10] 
0
source share

All Articles