【Ruby】mapメソッドをマスター | 他のメソッドとの違いも解説

みなさんこんにちは!

今回は、


悩める人
・Rubyのmap()メソッドの使い方をマスターしたい
・collect()メソッドやeach()メソッドとどう使い分けたらいいの?

というお悩みを解決する記事になっています。

Rubyでは、配列をスマートに取り扱うためのメソッドが数多く用意されています。

中でもmap()メソッドは、配列処理において最も利用されるであろうメソッドの1つで、map()メソッドの使い方を覚えれば、あらゆる配列処理をスムーズに実装できるようになります。

今回は、map()メソッドの使い方について、サンプルプログラムを適宜交えながら詳しく確認していきます。また、混同してしまいがちなその他のメソッドとの違いについても解説していますので、ぜひ最後まで読んでみてください。


傍楽たろう
なお、Rubyの魅力や特徴については以下の記事で詳しく解説していますので、こちらもぜひご覧くださいね。
おすすめ記事

みなさんこんにちは! 今回の記事は、 悩みを抱えた人 ・Rubyってどんなプログラミング言語なの?・Rubyを勉強すると将来役に立つかな?・Rubyのおすすめの学習方法が知りたい! というお悩みを解決する記事にな[…]

Rubyとは?その特徴や将来性、おすすめの学習方法まで網羅的にご紹介します

レバテックフリーランス … 業界最大級の案件数業界トップクラスの高単価報酬を誇る最大手のサービスです。実績豊富なコーディネーターが丁寧な対応をしてくれるため、案件の無理な提案はありません。フリーランスで生きていくためにはまず登録しておきましょう。
MidWorks … フリーランス賠償責任保障生命保険の折半など、フリーランスでありながらも正社員並みの保障を受けられるのが特徴です。また、経験豊富なキャリアコンサルタントによる手厚いサポートも受けられるため、安定したフリーランス生活を送りたい方には特におすすめのサービスです。
ポテパンフリーランス … IT業界・技術に詳しいコンサルタントが担当してくれるため、こちらの要望をきちんと理解した上で案件を紹介してくれます。また、案件情報のみならず、フリーランスのイロハについても教えてくれるため、フリーランスとして初めて活動される方には特におすすめのサービスです。

レバテックキャリア … ITエンジニアが利用したい転職エージェントNo.1にも選ばれており、年収600万円以上のハイクラス求人を5,000件以上も保有しています。エンジニアが転職を考えた時にまず初めに登録しておくべきサービスです。
Tech Stars Agent … Tech Stars Agentでは、担当エージェントが全員エンジニア出身のため、スキルやキャリアを見据えたきめ細かな転職支援が受けられます。運営元の株式会社Branding Engineerは、独立支援サービス「MidWorks」も展開しているため、独立を視野に入れたサポートも受けられます
転職ドラフト … 年収UP率93.8%/平均年収UP額126万円と圧倒的な年収UP率を誇るイベント型のエンジニア向け 転職サービスです。毎月1回開催され、厳選された優良IT/Web系企業約150社からダイレクトスカウトを受け取ることができます。年収アップを目指す方は登録必須です。


map()メソッドの利用方法

map()メソッドは、RubyのEnumerableモジュールの中で定義されているインスタンスメソッドです。

Enumerableモジュールは、簡単に言えば列挙されたオブジェクトを意味し、配列ハッシュ範囲オブジェクトなどがそれに当たります。

map()メソッドを配列で使う

それでは早速、実際のコードを見ながら確認してみましょう。

数値が格納された配列に対し、各要素を累乗したものを新しく配列として作成したいとします。この問題をmap()メソッドを使って実装したものが、以下のプログラムです。

# 配列を用意
sample_data = [3, 4, 12, 45, 51, 67, 87]

# map()メソッドを呼び出す
exp_data = sample_data.map { |data| 
  # 累乗を計算
  # 計算結果がそのまま新しい配列に要素として格納される
  data * data * data 
}

p exp_data # [27, 64, 1728, 91125, 132651, 300763, 658503]

map()メソッドの基本形は以下の通りです。

[配列の名前].map { |要素を格納する変数の名前| 実施する処理 }

map()メソッドは配列やハッシュなどをレシーバとして呼び出します。メソッドの中では、要素を格納する変数を利用して処理を実装します。戻り値は、最終的な計算結果が格納された配列です。

map()メソッドの実行結果について詳しく見てみましょう。

map()メソッドは、一度呼び出されると、ブロックの中で定義した処理をレシーバの要素数と同じだけ繰り返します。

今回の例を見てみると、初回実行時は1番目の要素に格納されている「3」をメソッド内で利用し、処理を行います。今回は累乗を計算(3乗)する処理を実装したため、したがって、新しく生成する配列の1番目の要素に「27」が格納されます。

これを各要素ごとに実行することで、最終的な戻り値「[27, 64, 1728, 91125, 132651, 300763, 658503]」が生成されます。これにてmap()メソッドの処理は完了です。

map()メソッドをハッシュで使う

ハッシュに対してもmap()メソッドを利用できます。

例として、ある生徒のテストの点数が格納されたハッシュから、各教科ごとの評価を行うプログラムを考えてみましょう。この問題をmap()メソッドで実装したものが、以下のプログラムです。

# 教科と点数が紐付けされたハッシュを用意
test_score = {
  japanese: 87,
  math: 91,
  science: 71,
  social_study: 49,
  english: 68
}

# ハッシュをレシーバとしてmap()メソッドを呼び出す
evalution = test_score.map { |subject, score|
  # テストの点数毎に評価値を設定
  case score
  when (81..100)
    ev_str = 'A'
  when (71..80)
    ev_str = 'B'
  when (61..70)
    ev_str = 'C'
  else
    ev_str = 'D'  
  end
  
  # 教科と評価を紐付ける
  [subject, ev_str]
}

# map()メソッドの戻り値はレシーバの種類によらず配列であることに注意
p evalution # [[:japanese, "A"], [:math, "A"], [:science, "B"], [:social_study, "D"], [:english, "C"]]

# to_h()メソッドでハッシュ化可能
p evalution.to_h # {:japanese=>"A", :math=>"A", :science=>"B", :social_study=>"D", :english=>"C"}

呼び出し方は配列の場合と変わりません。メソッド内の処理についてですが、ハッシュの場合はキーと値をそれぞれ変数として持つことができます。

配列で呼び出した場合と異なる点として、ハッシュをレシーバとしてmap()メソッドを呼び出しても、戻り値は配列であることに注意してください。

例えば今回の例であれば、教科名と点数毎に導出した評価値を配列として格納し、さらにそれを要素とした二次元配列を作成しています。もし、ハッシュとして取り扱いたい場合は、to_h()メソッドを呼び出してハッシュ化してあげましょう。

map()メソッドを省略して書いてみる

ある条件を満たしていれば、map()メソッドの中身をより少なくすることができます。その条件は、以下の通りです。

  • 引数が1つであること
  • 中身の処理が引数のないメソッドを呼び出すだけであること

例えば、数値型のデータが格納された配列が存在するとして、全ての文字列型に変換する処理を考えてみましょう。

map()メソッドの中身で、引数をレシーバとしてto_s()メソッドを呼び出せば目的の処理が実装できそうです。

ただし、引数が1つだけであり、引数が存在しないメソッドであるto_s()メソッドを呼び出すだけでよいので、以下のサンプルコードのように実装できます。

# 数値型のデータが格納された配列
sample_data = [36, 54.4, 1000, 3, -35, 900, -3.65, 1264, 44]

# 数値型の要素を全て文字列型に置き換える処理
sample_data_s_1 = sample_data.map(&:to_s)

for value in sample_data_s_1 do
  # class()メソッドで変数の型を取得
  # 文字列であれば「string」を返す
  p value + ":" + value.class.to_s
end

上記サンプルコードの実行結果は、以下の通りです。

"36:String"
"54.4:String"
"1000:String"
"3:String"
"-35:String"
"900:String"
"-3.65:String"
"1264:String"
"44:String"

サンプルコードの5行目に注目してください。5行目で実装した処理は、以下の処理と同様の動作をします。

# 省略しない書き方
sample_data_s_2 = sample_data.map { |value|
  value.to_s
}

1行で記載する方法は、最初は慣れないかもしれませんが、使っているうちにその便利さにどんどん気づいていくと思いますので、積極的に使ってみてくださいね。

map()メソッドと混同しやすいメソッドたち

Rubyでは、map()メソッド以外にも配列処理にうってつけのメソッドが複数用意されています。

中には、map()メソッドと動作が似ていることためにそれぞれを混同してしまうこともありますので、しっかりと違いを把握しておきましょう。

ここでは、動作が似ているメソッドを3つ取り上げ、map()メソッドとどのように違うのかを説明していきます。

collect()メソッドとの違い

1つ目は、collect()メソッドです。collect()メソッドは名前が異なるだけで動作自体はmap()メソッドと全くもって同じです。

以下のサンプルコードで、両者の呼び出した結果が同じであることを確認しましょう。

VERTICAL_LENGTH = 0
HORIZONTIAL_LENGTH = 1

# 縦と横の長さを持つ配列
vertical_horizontal_length_list = [
  [123, 456],
  [35, 35],
  [900, 231],
  [48, 90],
  [100, 100]
]

# 面積を計算し結果を配列化
# map()メソッドで実装した場合
area_list_1 = vertical_horizontal_length_list.map { |data|
  data[VERTICAL_LENGTH] * data[HORIZONTIAL_LENGTH]
}

# collect()メソッドで実装した場合
area_list_2 = vertical_horizontal_length_list.collect { |data|
  data[VERTICAL_LENGTH] * data[HORIZONTIAL_LENGTH]
}

p area_list_1 # [56088, 1225, 207900, 4320, 10000]
p area_list_2 # [56088, 1225, 207900, 4320, 10000]

map!()メソッドとの違い

2つ目は、map!()メソッドです。

map!()メソッドは、引数の扱い方や実装の仕方、戻り値が配列である点でmap()メソッドと変わりありません。大きく違う点は、レシーバ自体も戻り値と同様の配列に変更する仕様があることです。

Rubyでは、レシーバの情報をインスタンスメソッドを呼び出して変更することを「破壊的変更」と呼び、破壊的変更を行うメソッドを「破壊的メソッド」と呼びます。

以下のサンプルコードを実行して、map()メソッドとmap!()メソッド呼び出し後のレシーバの状態を確認しましょう。

LAST = 0
FIRST = 1

# 苗字と名前を持つ配列
name_list = [
  ['Suga', 'Yoshihide'],
  ['Abe', 'Shinzo'],
  ['Noda', 'Yoshihiko'],
  ['Kan', 'Naoto'],
  ['Hatoyama', 'Yukio'],
  ['Aso', 'Taro']
]

name_list.map {|data|
  data[LAST] + " " + data[FIRST]
}

# map()メソッド呼び出し後は変化なし
p name_list # [["Suga", "Yoshihide"], ["Abe", "Shinzo"], ["Noda", "Yoshihiko"], ["Kan", "Naoto"], ["Hatoyama", "Yukio"], ["Aso", "Taro"]]

name_list.map! {|data|
  data[LAST] + " " + data[FIRST]
}

# map!()メソッド呼び出し後は変更される
p name_list # ["Suga Yoshihide", "Abe Shinzo", "Noda Yoshihiko", "Kan Naoto", "Hatoyama Yukio", "Aso Taro"]

レシーバの情報をこれ以降使うことがなければ、新しく変数を用意せずに破壊的メソッドで書き換えてしまうのも一つの手です。

each()メソッドとの違い

3つ目は、each()メソッドです。

map()メソッドとの違いは、戻り値はレシーバ自身であるということです。

以下のサンプルコードでは、sample_dataという配列に対してeach()メソッドを呼びだし、その中で配列の各要素を2分の1にする処理を実装しています。

処理結果をresultという変数に代入していますが、結果は変わらず、sample_dataと同じ中身が表示されていることがお分かりいただけるかと思います。

sample_data = [48, 280, 458, 272, 4860, 478, 12, 96]

# 各要素に対して2分の1を算出

result = sample_data.each { |data|
  data / 2
}

p result # [48, 280, 458, 272, 4860, 478, 12, 96]

上記の処理のeach()をmap()に変更してみると、以下のようになります。

sample_data = [48, 280, 458, 272, 4860, 478, 12, 96]

# 各要素に対して2分の1を算出

result = sample_data.map { |data|
  data / 2
}

p result # [24, 140, 229, 136, 2430, 239, 6, 48]

これらを踏まえ、each()メソッドとmap()メソッドは以下の通り使い分けましょう。

  • 処理結果を後続の処理で使用する場合→map()メソッド
  • 処理結果を後続の処理で使用しない場合→each()メソッド

傍楽たろう
なお、配列を扱う際に便利なメソッドを以下の記事でまとめておりますので、こちらもぜひご覧くださいね。
おすすめ記事

今回は、Ruby初心者の方々に向けて、配列(Array)を扱う際に最低限知っておくべきメソッドをご紹介させていただきます。 Webアプリケーションを開発する中で、配列は必ずと言ってよいほど使用することとなるオブジェクトになります。 […]

プログラミングイメージ

お仕事の途中ですが、少し一休みして、転職独立について考えてみませんか🙌?

現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】

レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】

MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】

日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲

最後に

今回は、Rubyのmap()メソッドの使い方について解説させていただきました。

Rubyを使ったアプリケーションでは必ずと言って良いほど登場するメソッドですので、他の類似メソッドとの違いも含めてしっかりと理解しておきましょう。

最後にもう一度、本記事の内容を確認しておきましょう。

  • map()メソッドの特徴
    →配列やハッシュをレシーバにとる →処理結果を新たな配列に格納する
  • 他の類似メソッドとの違い →map()メソッドとcollect()メソッドは全く同じ →map()メソッドとmap!()メソッドは破壊的/非破壊的の違い →処理結果を後続の処理で使用するか否かでeach()メソッドと使い分ける

このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。

最後まで読んで頂きありがとうございました。