-1

I have been googling and not getting the real usage of the map method. Here is what i tried in my console.

a = ["kevin","john","ryan"]
a.each {|i| puts i.upcase}

The above would print the values in caps.

a = ["kevin","john","ryan"]
a.map {|i| puts i.upcase}

This would also print the values in caps. So what's so special in map method? and also can anyone lead me to a good source for this topic for better understanding.

Anshul Goyal
  • 73,278
  • 37
  • 149
  • 186
Kevin
  • 23,174
  • 26
  • 81
  • 111
  • 4
    Have you tried reading the documentation on those methods? For example: [Enumerable#map](http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-map). – Sergio Tulentsev Sep 06 '13 at 18:57
  • 1
    That's a `ruby` question, so i changed the tag and the title. – MurifoX Sep 06 '13 at 19:01
  • @SergioTulentsev: Apologize, I am pretty new to ruby and rails, the docs are not clear to me.... – Kevin Sep 06 '13 at 19:03
  • Just curious - which docs are you reading that are not clear? – Peter Alfvin Sep 06 '13 at 19:03
  • In the case of `map`, it will execute what's in the block and do the `puts` just like `each` does, but it is also collecting the return values of your `puts` statements into an array and returning that as a result. That result is useless to you, so there's no point in using `map` in that case. You'll get an array of `nil`s. `each` executes what's inside the block without collecting any results. – lurker Sep 06 '13 at 19:05
  • @mbratch : does it create a new array with the results, if so how can i access it. – Kevin Sep 06 '13 at 19:12
  • @Kevin Save it to a variable. – Dave Newton Sep 06 '13 at 19:13
  • @Kevin, yes, just assign it. – lurker Sep 06 '13 at 19:13

4 Answers4

1

map (or collect) collects the output of the block in an array.

Your example is not that useful; the block is used only for its side-effects.

Compare with a.map { |i| i.upcase } or, more Ruby-y, a.collect(&:upcase):

 > a2 = a.collect(&:upcase)
=> ['KEVIN', 'JOHN', 'RYAN']

each simply returns the value that it was called on, in this case, the original array.

Without the output of puts shown:

 > a2 = a.each(&:upcase)
=> ['kevin', 'john', 'ryan']

If you collect the output of puts you'll get an array of nils:

 > a2 = a.collect { |i| puts i.upcase }
=> [nil, nil, nil]
Dave Newton
  • 158,873
  • 26
  • 254
  • 302
1
  • Whatever is printed by puts is the side effect of evaluating the block. Regarding the side effect of evaluating the block, each and map are the same.

  • Besides side effect (which some methods do not have), all Ruby methods have a return value. It is the return value that differs between each and map. The former returns the receiver, the latter returns the result of the evaluation of the block.

    b = a.each{|i| puts i.upcase}
    b # => ["kevin","john","ryan"]
    
    b = a.map{|i| puts i.upcase}
    b # => ["KEVIN","JOHN","RYAN"]
    
sawa
  • 165,429
  • 45
  • 277
  • 381
1

In the case of map, it will execute what's in the block and do the puts just like each does, but it is also collecting the return values of your puts statements into an array and returning that as a result. That result is useless to you, so there's no point in using map in that case. You'll get an array of nils. each executes what's inside the block without collecting any results.

each executes the block and just returns the original array:

> a = ["kevin","john","ryan"]
=> ["kevin", "john", "ryan"]
> a.each {|i| puts i.upcase}
KEVIN
JOHN
RYAN
=> ["kevin", "john", "ryan"]
>

map executes the block and returns the values of what's in the block in an array:

> a = ["kevin","john","ryan"]
=> ["kevin", "john", "ryan"]
> a.map {|i| puts i.upcase}
KEVIN
JOHN
RYAN
 => [nil, nil, nil]
>

Note the returned array of nil, since each puts returned a nil and map collected those. If you want to collect the upper case strings in an array, you'd use map properly like this:

> a.map  {|i| i.upcase}
=> ['KEVIN', 'JOHN', 'RYAN']

Or, equivalently, a.map(&:upcase). You can also assign it to a variable:

> foo = a.map  {|i| i.upcase}
=> ['KEVIN', 'JOHN', 'RYAN']
> foo
=> ['KEVIN', 'JOHN', 'RYAN']
lurker
  • 56,987
  • 9
  • 69
  • 103
1

In your example, when you want to print each value individually right away, each is better. map is useful when you want to save the new versions your elements back into an array, and use that data later in your program.

For instance, say you wanted to forget whether the names had ever been lower or uppercase. Everywhere in your program, the names will be uppercase, so you just want to convert them to uppercase now and be done with that.

a = ["kevin","john","ryan"]
a = a.map{ |n| n.upcase }

Compare that to using each – you would have to use each_with_index, actually, since you need a way to assign the new values back:

a = ["kevin","john","ryan"]
a.each_with_index do |n, i|
  a[i] = n.upcase
end

This is the kind of situation map is for. Getting a new Enumerable with changed values, so that you can use the new values later.

By the way, as a shortcut, if you’re assigning a mapped variable back to itself, instead of a = a.map{}, you can use a.map!{}.

Another situation is if you wanted to print all names with an even number of letters. If you used each, here’s how it would look:

a = ["kevin","john","ryan"]
a.each do |n|
  if n.size.even?
    puts n.upcase
  end
end

If you used map, that would give you an array back. Then you could pass that array to select, and finally use each to print the values. This separates the steps more clearly for anyone reading the code, and makes your code more modular if you want to extend it later.

a = ["kevin","john","ryan"]
a.map(&:upcase) \
 .select{ |n| n.size.even? } \
 .each{ |n| puts n }

The last line could also be this:

 .each(&Kernel.method(:puts))

but that is probably unnecessarily confusing for such a small bit of code. What that does is convert the global puts method into a block using the & operator. Kernel holds all global methods, and .method is the way you get a variable with a method from a class. So .each(&Kernel.method(:puts)) says “get the global puts method, and turn it into a block so that puts is called with the block arguments as parameters”. But in this case, it’s clearer and simpler to write .each{ |n| puts n }.

Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
  • Thanks a lot, just at the last line a.map(&:upcase) instead of a.map(:upcase)... and also final question... each returns comma separated values and map and collect returns an array. – Kevin Sep 06 '13 at 19:30
  • 1
    I think you might mean something like this: `a.map(&:upcase).select { |n| n.size.even? } .each(&Kernel.method(:puts))` for that last example. – Nick Veys Sep 06 '13 at 19:35
  • @NickVeys You’re right, I had messed up `select` and `puts`. As for the `&:upcase` vs. `:upcase`, I thought that `map` also worked when given a symbol in later versions of Ruby, but it seems not. Maybe that’s just a Rails ActiveSupport extension. I fixed that too. – Rory O'Kane Sep 06 '13 at 19:42
  • 1
    @Kevin As for your final question, when you call the methods on an `Array`, both [`map`](http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-map) and [`collect`](http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-collect) (they are different names for the same method) return the new, changed `Array`. However, [`each`](http://ruby-doc.org/core-2.0.0/Array.html#method-i-each) doesn’t return “comma separated values” – it just returns the original, unchanged `Array` (which *is* usually displayed with commas between the values). So all three methods return `Array`s – some new, some old. – Rory O'Kane Sep 06 '13 at 19:48
  • If i am not troubling you too much, can you edit your answer and update this part on what .each(&Kernel.method(:puts)) Kernal.method does here. Thanks once again. – Kevin Sep 06 '13 at 19:50