🐒

Ruby mapメソッドの完全理解!

2024/07/28に公開

mapメソッドとは

mapメソッドの基本的な使い方

mapメソッドは、配列の各要素に対してブロックを適用し、新しい配列を返すメソッドです。
具体的に使い方を見ていきましょう。

array = [1, 2, 3, 4, 5]
new_array = array.map { |n| n * 2 }
# new_array は [2, 4, 6, 8, 10] になる

array配列に対して、mapメソッドで各要素にブロックを適応しています。
この場合、元のarrayは変更されずに新しい配列がnew_arrayに格納されます。

mapメソッドとeachメソッドの違い

ここでは、よくmapメソッドと比較されるeachメソッドとの違いについて説明していきます。
結論から述べると、eachメソッドでは新たな配列が作られません。

array = [1, 2, 3, 4, 5]
new_array = []
array.each{|n| new_array << n*2}
# new_array は [2, 4, 6, 8, 10] になる

先ほどのmapと比べると新たにnew_arrayというから配列を作らなければいけないのでやや冗長な書き方になります。
このような違いが生まれる原因として、戻り値の違いが挙げられます。
mapの戻り値は、ブロックを適応した後の値を返します。
つまり、上記の例でいくと[2, 4, 6, 8, 10]が戻り値になります。このため、新たな空配列を作る必要がなくなります。
これに対して、eachの戻り値はブロックを適応する前の配列がそのまま戻り値になります。
つまり、上記の例でいくと、[1, 2, 3, 4, 5]が戻り値になります。そのため、ブロックを適応した値を入れる新しい空の配列を必要とします。

使い分けの指針

・新しい配列を必要としない場合は、each
eachを使う指針としては、新しい配列を必要としていなくて、元の配列を回してデータの更新やログの出力など副作用的にループさせたい時などが適しています。
例えば、次のような場合です。

array = [1, 2, 3, 4, 5]
array.each { |n| puts "Number: #{n}" }
# 出力: 
# Number: 1
# Number: 2
# Number: 3
# Number: 4
# Number: 5
users = User.all
users.each do |user|
  user.update(active: true)
end

・変更後の配列を必要とするときはmap
mapとeachの最大の違いは、ブロックを適応した新しい配列が作られるかどうかです。
そのため、ブロックを適用した変更された配列が必要な場合はmapを使うのが適しています。
具体例が下記のような場合です。

array = [1, 2, 3, 4, 5]
doubled_array = array.map { |n| n * 2 }
# doubled_array は [2, 4, 6, 8, 10] になる

また、mapとeachの違いとしてコード量も上げられます。
基本的には、mapの方が短く書くことができるので、副作用的にループ処理をしたい場合以外はmapを使うのがいいと思います。

map!メソッド

mapメソッドは、元の配列を書き換えないイミュータブルという性質を持ちます。これによって、安全なデータ更新を行うことができます。
対して、map!メソッドは破壊的なメソッドです。破壊的なメソッドとは、元のデータを書き換える性質を持つメソッドのことです。このmap!を使うと下記のようになります。

array = [1, 2, 3, 4, 5]
array.map! { |n| n * 2 }

# 元の配列が変更される
p array  # => [2, 4, 6, 8, 10]

mapメソッドの省略法

mapのブロックでは、ブロックの内容を省略した書き方があります。それが下記のような書き方です。
&:メソッドor属性
このように&(アンパサンド)+:(シンボル)+メソッドor属性と記述することで配列の各要素にメソッドを適応したり、配列のある属性を取り出せたりします。
いくつか例を紹介します。

・strings配列の要素を全て大文字にする時の省略記法。

strings = ["apple", "banana", "cherry"]
upcased_strings = strings.map(&:upcase)
# upcased_strings は ["APPLE", "BANANA", "CHERRY"]

・オブジェクトの配列からある属性を抽出する例

users = [User.new(name: "Alice"), User.new(name: "Bob"), User.new(name: "Carol")]
user_names = users.map(&:name)
# user_names は ["Alice", "Bob", "Carol"]

このように記述するにはいくつか条件があります。
①メソッドが引数なしで呼び出せるものに限る
②メソッドが呼び出されるインスタンス(配列の要素)のインスタンスメソッドである

この条件を満たす場合は、省略記法(&:)を使うことができます。

Discussion