【Ruby】mapとselectで配列を操作する方法(JavaScriptのmap/filterとの対比)

Ruby

こんにちは、かつコーチです。

「JavaScriptのmapやfilterは使ったことがあるけど、Rubyだと何て書くの?」。

他言語の経験がある方から、こうした質問をよく受けます。

今回は、Rubyで配列を操作する代表的なメソッドmapselectrejectreduceを、JavaScriptとの対比を交えて解説します。

読み終える頃には、eachで書いていたループを、より簡潔なメソッドチェーンに置き換えられるようになります。

基本の書き方

mapメソッドの使い方

mapメソッドは「配列の各要素に処理を行い、その結果を新しい配列として返す」メソッドです。

numbers = [1, 2, 3, 4, 5]

doubled = numbers.map { |n| n * 2 }
puts doubled.inspect # => [2, 4, 6, 8, 10]

元のnumbers配列は変更されず、doubledという新しい配列が作られる点がポイントです。

selectとrejectメソッドの使い方

selectメソッドは「条件に合う要素だけを集めた新しい配列を返す」メソッドです。

反対にrejectメソッドは「条件に合わない要素だけを集めた新しい配列を返す」メソッドです。

numbers = [1, 2, 3, 4, 5, 6]

evens = numbers.select { |n| n.even? }
puts evens.inspect # => [2, 4, 6]

odds = numbers.reject { |n| n.even? }
puts odds.inspect # => [1, 3, 5]

selectrejectは、条件がまったく逆になるだけで、書き方はほぼ同じです。

reduceメソッドの使い方

reduceメソッドは「配列の要素を1つの値にまとめる」メソッドです。

injectという別名でも呼ばれます。

numbers = [1, 2, 3, 4, 5]

total = numbers.reduce(0) { |sum, n| sum + n }
puts total # => 15

reduce(0)0初期値で、ブロックのsumに最初に渡される値です。

ループのたびに、ブロックの戻り値が次のsumとして引き継がれていきます。

JavaScriptのmap/filterとの対比表

他言語の経験がある方向けに、JavaScriptとの対比表をまとめます。

処理内容RubyJavaScript
各要素を変換するnumbers.map { \|n\| n * 2 }numbers.map(n => n * 2)
条件に合う要素を集めるnumbers.select { \|n\| n.even? }numbers.filter(n => n % 2 === 0)
条件に合わない要素を除くnumbers.reject { \|n\| n.even? }numbers.filter(n => n % 2 !== 0)
1つの値にまとめるnumbers.reduce(0) { \|sum, n\| sum + n }numbers.reduce((sum, n) => sum + n, 0)

JavaScriptにselectrejectという個別のメソッドはなく、filterの条件式を反転させて対応する点が大きな違いです。

RubyはRubyで用意された専用メソッドが多く、目的に応じたメソッド名を選ぶことで、コードの意図が読みやすくなります。

よくあるつまずきポイント・エラー対処

mapとeachを混同してしまう

mapeachは似ていますが、戻り値がまったく異なります。

# ❌Before:eachの戻り値を配列だと思って使ってしまう
numbers = [1, 2, 3]

result = numbers.each { |n| n * 2 }
puts result.inspect # => [1, 2, 3](変換前の配列がそのまま返る)

私は業務でこのミスをして、「なぜ倍にした値が反映されないんだろう」と30分ほど原因を探した経験があります。

eachはあくまで「繰り返し処理を行う」だけのメソッドで、戻り値は元の配列そのものです。

# ✅After:値を変換して新しい配列が欲しいときはmapを使う
numbers = [1, 2, 3]

result = numbers.map { |n| n * 2 }
puts result.inspect # => [2, 4, 6]

「配列の中身を変換したい」ときはmap、「単に繰り返し処理をしたいだけ」のときはeach、と目的で使い分けることが大切です。

select!とselectを間違えて元データを壊してしまう

selectには、末尾に!が付いた破壊的メソッドselect!もあります。

# ❌Before:select!で元の配列そのものを書き換えてしまう
numbers = [1, 2, 3, 4, 5]

numbers.select! { |n| n.even? }
puts numbers.inspect # => [2, 4](元の配列が上書きされている)

ある時、後続の処理で元の配列(numbers)を全件参照するつもりが、select!によって中身が絞り込まれてしまい、想定外の件数しか処理されないというバグを起こしたことがあります。

原因は、末尾の!を見落としていたことでした。

# ✅After:元の配列を残したい場合は!なしのselectを使う
numbers = [1, 2, 3, 4, 5]

evens = numbers.select { |n| n.even? }
puts numbers.inspect # => [1, 2, 3, 4, 5](元の配列はそのまま)
puts evens.inspect   # => [2, 4]

Rubyでは「!が付くメソッドは元のオブジェクトを変更する可能性がある」という慣習があります。

元のデータを残したいときは、!が付いていないメソッドを使うようにしましょう。

応用・一歩先の使い方

メソッドチェーンで複数の処理をつなげる

mapselectreduceは、メソッドチェーンでつなげて使うと真価を発揮します。

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 偶数だけを2倍にして、合計を求める
result = numbers.select { |n| n.even? }
                .map { |n| n * 2 }
                .reduce(0) { |sum, n| sum + n }

puts result # => 60

「絞り込む → 変換する → 集計する」という一連の流れを、変数を都度用意せずに1つの式として書けます。

for文とif文で同じ処理を書くよりも、何をしているかが一目で分かるコードになります。

map以外のバリエーション:flat_mapとfilter_map

mapには、目的に応じたバリエーションもあります。

# flat_map:mapした結果の配列をさらに平坦化する
words = ["Ruby is fun", "I love coding"]
all_words = words.flat_map { |w| w.split(" ") }
puts all_words.inspect # => ["Ruby", "is", "fun", "I", "love", "coding"]

# filter_map:絞り込みと変換を同時に行う(Ruby 2.7以降)
numbers = [1, 2, 3, 4, 5, 6]
result = numbers.filter_map { |n| n * 10 if n.even? }
puts result.inspect # => [20, 40, 60]

filter_mapを使うと、これまでselectmapを2回に分けて書いていた処理を1行にまとめられます。

Ruby 3.4系でも問題なく使えるメソッドなので、積極的に活用していきましょう。

まとめ

この記事のポイント

  • mapは「変換した新しい配列」、eachは「繰り返し処理のみ」で戻り値が異なる
  • selectは条件に合う要素、rejectは条件に合わない要素を集める
  • reduceは配列を1つの値にまとめるメソッド
  • !付きの破壊的メソッド(select!など)は元のデータを書き換えるため注意が必要
  • メソッドチェーンやfilter_mapを使うと、より簡潔なコードが書ける

次に読むべき記事

前回の「for・while・timesで繰り返し処理」と合わせて読むと、Rubyの繰り返し処理の全体像がつかめます。

次は「メソッドの定義と引数」で、自分でメソッドを作る方法を学んでいきましょう。

タグ: #Ruby #中級者向け #基本文法

タイトルとURLをコピーしました