3

It seems like there are a lot of ways to loop over an Enumerable in Ruby. Are there any differences between each, foreach, or in collect, map or other similar methods?

Or is there a reason I shouldn't use certain methods in certain situations?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Addison
  • 3,791
  • 3
  • 28
  • 48

3 Answers3

6

collect/map are equivalent. They differ from each in that each only executes the block for each element, whereas collect/map return an array with the results of calling the block for each element. Another way to put it might be, each is expected to do something with each element, whereas map is expected to transform each element (map it onto something else).

You could use collect or map anywhere each is used, and your code will still work. But it will probably be slightly less efficient because it collects the results in an array, unless your Ruby implementation realizes it doesn't have to bother creating an array because it's never used.

Another reason to use each instead of map or collect is to help out anyone reading your code. If I see each then I can be like okay, we're about to use each element of the data to do something. If I see map then I'm expecting to see new data being created, based on a remapping of the old data.

With regards to map vs. collect I would say it's a matter of preference, but you should pick one and stick with it for consistency.

miorel
  • 1,863
  • 2
  • 14
  • 18
  • "`each` is expected to do something with each element, whereas `map` is expected to transform each element" Isn't transforming also a form of doing? The difference is not in what to do, but in what to return. – SasQ Aug 23 '13 at 06:17
1

Array#collect (and Array#map) return a new array based on the code passed in the block. Array#each performs an operation (defined by the block) on each element of the array.

I would use collect like this:

array = [1, 2, 3]
array2 = array.collect {|val| val + 1}

array.inspect # => "[1, 2, 3]"
array2.inspect # => "[2, 3, 4]"

And each like this:

array = [1, 2, 3]
array.each {|val| puts val + 1 }
# >> 2
# >> 3
# >> 4
array.inspect # => "[1, 2, 3]"
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
1

Using pry and Rubinus (it's easier to read ruby code) take a look for yourself

$ pry
[1] pry(main)> cd Enumerable
[2] pry(Enumerable):1> show-method collect

From: .../rbx-head/kernel/common/enumerable18.rb @ line 4:
Owner: Enumerable
Visibility: public
Number of lines: 9

def collect
  if block_given?
    ary = []
    each { |o| ary << yield(o) }
    ary
  else
    to_a
  end
end

[3] pry(Enumerable):1> show-method map

From: .../rbx-head/kernel/common/enumerable18.rb @ line 4:
Owner: Enumerable
Visibility: public
Number of lines: 9

def collect
  if block_given?
    ary = []
    each { |o| ary << yield(o) }
    ary
  else
    to_a
  end
end
[4] pry(Enumerable):1>

So nope not for these two.

map and collect iterate over a collection. For each object it executes your block then collects the result. The each methods do not collect the results

Paul.s
  • 38,494
  • 5
  • 70
  • 88