Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences between [1,2,3].to_enum and [1,2,3].enum_for in Ruby

In Ruby I'm trying to understand between the to_enum and enum_for methods. Before I my question, I've provided some sample code and two examples to help w/ context.

Sample code:

# replicates group_by method on Array class
class Array
  def group_by2(&input_block)
    return self.enum_for(:group_by2) unless block_given?
    hash  = Hash.new {|h, k| h[k] = [] }
    self.each { |e| hash[ input_block.call(e) ] << e }
    hash
  end
end

Example # 1:

irb (main)> puts [1,2,3].group_by2.inspect
=> #<Enumerator: [1, 2, 3]:group_by2> 

In example #1: Calling group_by on the array [1,2,3], without passing in a block, returns an enumerator generated with the command self.enum_for(:group_by_2).

Example #2

irb (main)> puts [1,2,3].to_enum.inspect
=> #<Enumerator: [1, 2, 3]:each> 

In example #2, the enumerator is generated by calling the to_enum method on the array [1,2,3]

Question:

Do the enumerators generates in examples 1 and 2, behave differently in any way? I can see from the inspected outputs that they show slightly different labels, but I can find any difference in the enumerators' behavior.

# Output for example #1
#<Enumerator: [1, 2, 3]:each> # label reads ":each"

# Output for example #2
#<Enumerator: [1, 2, 3]:group_by2> # label reads ":group_by2"
like image 390
popedotninja Avatar asked May 24 '14 22:05

popedotninja


1 Answers

p [1, 2, 3].to_enum
p [1, 2, 3].enum_for

--output:--

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

From the docs:

to_enum

Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

...

enum_for

Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

ruby is a language that often has method names that are synonyms.

Followup question:

Does the symbol in the command [1,2,3].to_enum(:foo) serve a purpose, other than replacing :each with :foo in the output?

Yes. By default, ruby hooks up the enumerator to the receiver's each() method. Some classes do not have an each() method, for instance String:

str = "hello\world"
e = str.to_enum
puts e.next

--output:--
1.rb:3:in `next': undefined method `each' for "helloworld":String (NoMethodError)
    from 1.rb:3:in `<main>

to_enum() allows you to specify the method you would like the enumerator to use:

str = "hello\nworld"
e = str.to_enum(:each_line)
puts e.next

--output:--
hello

Now, suppose you have the array [1, 2, 3], and you want to to create an enumerator for your array. An array has an each() method, but instead of creating an enumerator with each(), which will return each of the elements in the array, then end; you want to create an enumerator that starts over from the beginning of the array once it reaches the end?

e = [1, 2, 3].to_enum(:cycle)

10.times do
  puts e.next()
end

--output:--
1
2
3
1
2
3
1
2
3
1
like image 92
7stud Avatar answered Nov 12 '22 14:11

7stud