274

What does .map do in:

params = (0...param_count).map
starball
  • 20,030
  • 7
  • 43
  • 238
bigpotato
  • 26,262
  • 56
  • 178
  • 334
  • 10
    Ask *one* question at a time. `map` is a common "functional" method found on Enumerable objects used for transforming the values in a sequence (with special considerations). `..` and `...` are ways of creating ranges. Also, *get familiar* with the REPL, where you can try this stuff out yourself! :) –  Aug 23 '12 at 03:59
  • 6
    REPL for ruby is irb, for Rails it is rails c. REPL allows you to test code directly against the language shell itself. – Gary Mar 13 '14 at 15:16

8 Answers8

455

The map method takes an enumerable object and a block, and runs the block for each element, outputting each returned value from the block (the original object is unchanged unless you use map!):

[1, 2, 3].map { |n| n * n } #=> [1, 4, 9]

Array and Range are enumerable types. map with a block returns an Array. map! mutates the original array.

Where is this helpful, and what is the difference between map! and each? Here is an example:

names = ['danil', 'edmund']

# here we map one array to another, convert each element by some rule
names.map! {|name| name.capitalize } # now names contains ['Danil', 'Edmund']

names.each { |name| puts name + ' is a programmer' } # here we just do something with each element

The output:

Danil is a programmer
Edmund is a programmer
Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
  • 3
    thanks speransky for the example. then how is .map different from .each? – bigpotato Aug 23 '12 at 04:06
  • 3
    Ahhh I get it. So .map actually mutates the array while .each just loops through the array to access the values while leaving the original array untouched? – bigpotato Aug 23 '12 at 04:18
  • I don't think so. `each` and `map` works the same way, but while `each` is an iterator only, `map` has `map!` form, which mutates an object given. Also `map` has a `collect` synonym with no differences. – lifecoder Apr 29 '13 at 07:26
  • @SperanskyDanil, it is about @Edmund's comment, which you could see right above mine, for this part: "So .map actually mutates the array while .each just loops". `#map` actually doesn't mutate anything, `#map!` does. – lifecoder Apr 30 '13 at 09:57
  • 25
    It is hazardous for casual readers that the opening sentence describes `map` as though it were `map!` – kaleidic Aug 12 '13 at 14:56
  • wasn't wrong, could just be a little more explicit. Updated Q – Michael Durrant Nov 05 '13 at 17:42
  • 12
    to see the difference between map and each, open an IRB window and look at the results for y and z in the following code: y = [1,2,3].each {|x| x + 1}; z = [1,2,3].map {|x| x + 1} – davej Nov 27 '13 at 19:50
  • @davej: Whoa, I got different answers from both of them, but I didn't understand why is this so? – inquisitive Dec 02 '14 at 05:28
  • 7
    @Inquisitive: 'each' returns the array that calls it (in the example, [1,2,3]) when a block is supplied, 'map' returns a new array populated with the values calculated by the block. This might help: set the variable ary = [1,2,3], and check out it's object_id. Then run y = ary.each {|x| x + 1}; z = ary.map {|x| x + 1}. Now check the object_id's of y and z. y has the same object_id as ary (because each returned ary), but z has a different object_id, because map returned a new array. – davej Dec 13 '14 at 16:52
  • great answer, thanks! This inspired a visual answer for visual thinkers, also trying to remove some confusion around `map` and `map!`, see below https://stackoverflow.com/a/66869517/5925094 – Rich Steinmetz Mar 30 '21 at 11:02
71

map, along with select and each is one of Ruby's workhorses in my code.

It allows you to run an operation on each of your array's objects and return them all in the same place. An example would be to increment an array of numbers by one:

[1,2,3].map {|x| x + 1 }
#=> [2,3,4]

If you can run a single method on your array's elements you can do it in a shorthand-style like so:

  1. To do this with the above example you'd have to do something like this

    class Numeric
      def plusone
        self + 1
      end
    end
    [1,2,3].map(&:plusone)
    #=> [2,3,4]
    
  2. To more simply use the ampersand shortcut technique, let's use a different example:

    ["vanessa", "david", "thomas"].map(&:upcase)
    #=> ["VANESSA", "DAVID", "THOMAS"]
    

Transforming data in Ruby often involves a cascade of map operations. Study map & select, they are some of the most useful Ruby methods in the primary library. They're just as important as each.

(map is also an alias for collect. Use whatever works best for you conceptually.)

More helpful information:

If the Enumerable object you're running each or map on contains a set of Enumerable elements (hashes, arrays), you can declare each of those elements inside your block pipes like so:

[["audi", "black", 2008], ["bmw", "red", 2014]].each do |make, color, year|
  puts "make: #{make}, color: #{color}, year: #{year}"
end
# Output:
# make: audi, color: black, year: 2008
# make: bmw, color: red, year: 2014

In the case of a Hash (also an Enumerable object, a Hash is simply an array of tuples with special instructions for the interpreter). The first "pipe parameter" is the key, the second is the value.

{:make => "audi", :color => "black", :year => 2008}.each do |k,v|
    puts "#{k} is #{v}"
end
#make is audi
#color is black
#year is 2008

To answer the actual question:

Assuming that params is a hash, this would be the best way to map through it: Use two block parameters instead of one to capture the key & value pair for each interpreted tuple in the hash.

params = {"one" => 1, "two" => 2, "three" => 3}
params.each do |k,v|
  puts "#{k}=#{v}"
end
# one=1
# two=2
# three=3
boulder_ruby
  • 38,457
  • 9
  • 79
  • 100
  • This doesn't work for me in irb. I get `NoMethodError: private method 'plusone' called for 1:Fixnum` in ruby 2 and 'wrong number of args' in ruby 1.9/1.8. Anyway, I used a lambda: `plusone = ->(x) { x + 1 }` then take out the symbol specifier: `[1,2,3].map(&plusone)`. – tjmcewan Jun 26 '14 at 01:52
  • 1
    hmm sounds like you declared `private` inside of the class where you put your method before you put your method – boulder_ruby Jun 26 '14 at 03:05
  • Yeah, it totally does. Except it wasn't. :( First of all it was in a straight script w/o classes, secondly in plain irb. Here's my copy/paste of your code: https://gist.github.com/tjmcewan/a7e4feb2976a93a5eef9 – tjmcewan Jun 27 '14 at 03:34
  • Yeah, I totally just put a bad example in my code I'm sorry. Try the modified code. It works now... – boulder_ruby Jun 27 '14 at 15:50
  • 1
    @boulder_ruby is there a way to do this with a normal method — as in, not a class method? – tekknolagi Jul 01 '14 at 00:32
  • @tekknolagi none of the code above uses class methods. Class methods are declared inside a class by this code `def self.methodnam`. What I've done above is to create *instance* methods via `def methodnam`. – boulder_ruby Jan 26 '16 at 02:23
  • @boulder_ruby It's been quite some time since I asked that question, but I believe I meant "Can one map over an Enumerable with a method outside of a class?" But it doesn't matter much any more :) – tekknolagi Jan 26 '16 at 06:38
  • I need to use multiple attributes inside the map like [1,2,3].map(&plusone,.....), how can do this because I have used it and getting error. – Rahul Gupta Nov 09 '22 at 04:10
12

For anyone who needs to visualize it:

Map method example explained visually

Map that does nothing

Map that does even less

Map with a bang!

Here's the full story:

https://richstone.io/rubys-map-collect-methods-explained-visually/

Rich Steinmetz
  • 1,020
  • 13
  • 28
7

Using ruby 2.4 you can do the same thing using transform_values, this feature extracted from rails to ruby.

h = {a: 1, b: 2, c: 3}

h.transform_values { |v| v * 10 }
 #=> {a: 10, b: 20, c: 30}
tokhi
  • 21,044
  • 23
  • 95
  • 105
4

0..param_count means "up to and including param_count". 0...param_count means "up to, but not including param_count".

Range#map does not return an Enumerable, it actually maps it to an array. It's the same as Range#to_a.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Pedro Nascimento
  • 13,136
  • 4
  • 36
  • 64
3

It "maps" a function to each item in an Enumerable - in this case, a range. So it would call the block passed once for every integer from 0 to param_count (exclusive - you're right about the dots) and return an array containing each return value.

Here's the documentation for Enumerable#map. It also has an alias, collect.

Ry-
  • 218,210
  • 55
  • 464
  • 476
3

#each

#each runs a function for each element in an array. The following two code excerpts are equivalent:

x = 10
["zero", "one", "two"].each{|element|
    x++
    puts element
}
x = 10
array = ["zero", "one", "two"]

for i in 0..2
    x++
    puts array[i]
end

#map

#map applies a function to each element of an array, returning the resulting array. The following are equivalent:

array = ["zero", "one", "two"]
newArray = array.map{|element| element.capitalize()}
array = ["zero", "one", "two"]

newArray = []
array.each{|element|
    newArray << element.capitalize()
}

#map!

#map! is like #map, but modifies the array in place. The following are equivalent:

array = ["zero", "one", "two"]
array.map!{|element| element.capitalize()}
array = ["zero", "one", "two"]
array = array.map{|element| element.capitalize()}
Jivan Pal
  • 182
  • 10
2

Map is a part of the enumerable module. Very similar to "collect" For Example:

  Class Car

    attr_accessor :name, :model, :year

    Def initialize (make, model, year)
      @make, @model, @year = make, model, year
    end

  end

  list = []
  list << Car.new("Honda", "Accord", 2016)
  list << Car.new("Toyota", "Camry", 2015)
  list << Car.new("Nissan", "Altima", 2014)

  p list.map {|p| p.model}

Map provides values iterating through an array that are returned by the block parameters.

gkstr1
  • 51
  • 3