I think it's so cool that the Ruby map
method exists. It means that Ruby has support for higher-order functions, and functional programming.
Here's the definition of higher-order functions.
Higher-order functions are functions that accept a function as an argument and/or return a function as a return value.
How does the map method work
The way the map
method works in Ruby is, it takes an enumerable object, (i.e. the object you call it on), and a block.
Then, for each of the elements in the enumerable, it executes the block, passing it the current element as an argument. The result of evaluating the block is then used to construct the resulting array.
In other words:
Applying
map
on an array returns a new array where each element is the result of evaluating the block with the element as an argument.
As you can see, the block plays the role of the function in Ruby. It's actually a function object (or a functor), but that's just a side note.
An example might make it easier to understand.
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
In this example, the block (i.e. { |n| n * 2 }
) is applied to each element of the initial array (i.e. [1, 2, 3]
) in turn, resulting in a new array.
What does map(&:name) mean in Ruby?
It's a shorthand notation, and it's useful when all you need to do inside the block is call a method.
[1, 2, 3].map { |n| n.even? }
# could be written as
[1, 2, 3].map(&:even?)
If you want to know how this works, take a look at What does &block (ampersand parameter) mean?.
Map over a Hash
There's a new method (since Ruby 2.4) called Hash#transform_values
that you could use pretty much like you would use an Array
.
h = { a: 1, b: 2, c: 3 }
h.transform_values {|v| v * v + 1 }
# => { a: 2, b: 5, c: 10 }
h.transform_values(&:to_s)
# => { a: "1", b: "2", c: "3" }
Map over a nested Array
If you have a 2D array (a matrix) and you want to map over it, you would do it like this.
my_array = [
[1, 2, 3, 4],
[5, 6, 7, 8],
]
my_array.map { |row| row.map { |col| col + 1 } }
# => [[2, 3, 4, 5], [6, 7, 8, 9]]
It will return a new array. If you want to change the values of the initial array, you can use map!
.
Map and remove nil values
The resulting array could contain a few nil
values, and you might want to get rid of them. Here are a few ways you could do it.
[1, 2, 3].map { |n| n == 2 ? n : nil } # => [nil, 2, nil]
[1, 2, 3].select { |n| n == 2 ? n : nil } # => [2]
[1, 2, 3].map { |n| n == 2 ? n : nil }.compact # => [2]
[1, 2, 3].map { |n| n == 2 ? n : nil } - [nil] # => [2]
[1, 2, 3].map { |n| n == 2 ? n : nil }.reject(&:nil?) # => [2]
[1, 2, 3].reduce([]) do |memo, n|
memo << n if n == 2
memo
end
How to map with an index
Sometimes you need an index. Maybe you want to append the index to the value. Or maybe you have a better reason :)
["a", "b", "c"].each_with_index.map { |n, i| n + i.to_s } # => ["a0", "b1", "c2"]
["a", "b", "c"].map.with_index { |n, i| n + i.to_s } # => ["a0", "b1", "c2"]
Map vs. each
You use map
to collect the result of running the block over the elements of the array. And you use each
to run the block over the elements without collecting the values. You can read more about using the each
method here.
Map vs. collect
They are aliases for each other, so there is no difference. But map
is a more popular name for what it's doing than collect
is.
Map vs. select
select
is different than the ruby map
method. What select
does is it returns all the objects for which the block returns a truthy value (i.e. not nil
or false
).