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

みなさんこんにちは!

今回は、


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

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

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

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

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


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

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

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

フリーランス案件を探すならこのエージェント!

運営会社 レバテック株式会社 株式会社Branding Engineer 株式会社Hajimari
対応エリア 東京・神奈川・埼玉・千葉
大阪・兵庫・京都・愛知・福岡
東京・神奈川・埼玉・千葉
大阪・兵庫・京都・奈良・和歌山・滋賀
東京・神奈川・埼玉・千葉
茨城・大阪・兵庫・福岡
案件数 約28,000件 約3,300件 約4,200件
平均単価 68.2万円 72.2万円
最高月収 145万円 200万円 176万円
支払いサイト 15日(月末締め翌月15日支払い) 20日
手数料(中間マージン) 非公開 非公開
※実質20%
非公開
特徴高単価な案件が多い
福利厚生サービスが受けられる
・首都圏の案件が中心
給与保証制度で安定した収入を得られる
・税務処理のサポートあり
・マージン率は実質20%
週2/週3の案件が多い
在宅/リモート案件が多い
・スタートアップ企業やベンチャー企業の案件が多い
紹介企業例 ・株式会社Gunosy
・株式会社バンダイナムコホールディングス
・株式会社ユーザベース
・Sansan株式会社
・株式会社一休
・株式会社FiNC Technologies など
・クックパッド株式会社
・株式会社カオナビ
・株式会社Kaizen Platform など
説明記事
公式サイト


転職を考えているならこのエージェント!

運営会社 レバテック株式会社 株式会社Branding Engineer 株式会社リブセンス
対応エリア 東京・神奈川・埼玉・千葉
大阪・兵庫・京都・愛知・福岡
東京・神奈川・埼玉・千葉
大阪・兵庫・京都・奈良・和歌山・滋賀
東京・神奈川・埼玉・千葉
茨城・大阪・兵庫・福岡
案件数 約13,000件 約800件
※非公開求人が多い

※ドラフト制のため、対象外
年齢層 20代:◎
30代:〇
40代:〇
20代:〇
30代:〇
40代:△
20代:〇
30代:〇
40代:〇
特徴エンジニア・デザイナーを専門としている
・カウンセリングに時間をかける
年収アップ率60%の実績
IT/Web/ゲーム業界専門の転職エージェント
・業界経験者がキャリアコンサルタントを務める
・丁寧なカウンセリングで入社後の離職率1%以下を実現
ドラフト制による採用を叶える特殊なサービス
スキル・経験重視で評価してもらえる
・他エンジニアの入札結果を見ることで
自分の市場価値が分かる
紹介企業例 ・サイボウズ株式会社
・株式会社サイバーエージェント
・Chatwork株式会社 など
・DMM.com
・Money Forward
・SmartHR など
・メルカリ
・ZOZOテクノロジーズ
・freee など
説明記事
公式サイト



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アプリケーションを開発する中で、配列は必ずと言ってよいほど使用することとなるオブジェクトになります。 […]

プログラミングイメージ

最後に

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

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

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

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

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

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