Ruby メソッドチェーン実践:select・map・inject の活用パターン

スポンサーリンク

Ruby メソッドチェーン実践:select・map・inject の活用パターン

Ruby の Enumerable メソッドはチェーンでつなぐことで、複雑な処理を宣言的かつ簡潔に書けます。実際のコードでよく登場するパターンをまとめます。


カートの合計金額を計算する

在庫あり・数量1以上の商品だけ対象にして合計金額を出す例です。

cart = [
  { name: 'apple',  price: 100, qty: 2, in_stock: true },
  { name: 'banana', price:  80, qty: 0, in_stock: false }, # 数量0
  { name: 'cherry', price: 200, qty: 1, in_stock: true },
  { name: 'grape',  price: 300, qty: 1, in_stock: false }, # 在庫なし
]

total = cart
  .select { |item| item[:in_stock] && item[:qty] > 0 } # 有効な商品だけ絞り込む
  .sum { |item| item[:price] * item[:qty] }             # 小計を合計
# => 400(apple: 200 + cherry: 200)

Ruby では mapsumsum のブロックにまとめられるため、JavaScript より簡潔になります。


タグの出現ランキングを作る

記事に付いたタグを集計して、出現回数順のランキングを作る例です。

articles = [
  { title: '記事A', tags: %w[ruby rails rspec] },
  { title: '記事B', tags: %w[ruby gem] },
  { title: '記事C', tags: %w[rails rspec] },
  { title: '記事D', tags: %w[ruby rails] },
]

top3 = articles
  .flat_map { |a| a[:tags] }            # タグを1つの配列に展開
  .tally                                  # { "ruby"=>3, "rails"=>3, "rspec"=>2, "gem"=>1 }
  .sort_by { |_, count| -count }         # 出現回数の降順
  .first(3)                               # 上位3件
  .map { |tag, count| { tag:, count: } } # 整形(Ruby 3.1+ のショートハンド)

# => [{ tag: "ruby", count: 3 }, { tag: "rails", count: 3 }, { tag: "rspec", count: 2 }]

tally は Ruby 2.7 以降で使えます。


アクティブユーザーのスコアランキングを作る

有効なユーザーだけを対象にスコア降順で上位3件を取り出し、順位付きで整形する例です。

users = [
  { name: 'Alice', score: 80, active: true },
  { name: 'Bob',   score: 95, active: false }, # 無効
  { name: 'Carol', score: 72, active: true },
  { name: 'Dave',  score: 88, active: true },
  { name: 'Eve',   score: 91, active: true },
]

ranking = users
  .select { |u| u[:active] }              # 有効なユーザーだけ
  .sort_by { |u| -u[:score] }             # スコア降順
  .first(3)                                # 上位3件
  .map.with_index(1) do |u, rank|         # 順位付きで整形
    { rank:, name: u[:name], score: u[:score] }
  end

# => [
#   { rank: 1, name: "Eve",   score: 91 },
#   { rank: 2, name: "Dave",  score: 88 },
#   { rank: 3, name: "Alice", score: 80 },
# ]

ユーザー別の売上合計を出す

注文データをユーザーごとにまとめて、売上の多い順に並べる例です。

orders = [
  { user: 'Alice', amount: 1000 },
  { user: 'Bob',   amount:  500 },
  { user: 'Alice', amount: 2000 },
  { user: 'Carol', amount: 1500 },
  { user: 'Bob',   amount:  800 },
]

ranking = orders
  .group_by { |o| o[:user] }                              # ユーザーごとにグループ化
  .transform_values { |os| os.sum { |o| o[:amount] } }   # 各ユーザーの合計を計算
  .sort_by { |_, total| -total }                          # 売上降順
  .map { |user, total| { user:, total: } }                # 整形

# => [
#   { user: "Alice", total: 3000 },
#   { user: "Carol", total: 1500 },
#   { user: "Bob",   total: 1300 },
# ]

group_bytransform_valuessort_by は集計系で非常によく使うパターンです。


カテゴリ別の最高スコア商品を取り出す

カテゴリごとに最も評価の高い商品を1件ずつ取り出す例です。

products = [
  { name: 'MacBook', category: 'pc',    score: 90 },
  { name: 'ThinkPad', category: 'pc',   score: 85 },
  { name: 'iPhone',   category: 'phone', score: 95 },
  { name: 'Pixel',    category: 'phone', score: 88 },
  { name: 'iPad',     category: 'tablet', score: 82 },
]

best_per_category = products
  .group_by { |p| p[:category] }         # カテゴリごとにグループ化
  .transform_values { |ps| ps.max_by { |p| p[:score] } } # 各カテゴリの最高スコア商品
  .values                                 # ハッシュの値だけ取り出す
  .sort_by { |p| -p[:score] }            # スコア降順

# => [
#   { name: "iPhone",  category: "phone",  score: 95 },
#   { name: "MacBook", category: "pc",     score: 90 },
#   { name: "iPad",    category: "tablet", score: 82 },
# ]

チェーンを書くときのポイント

# ❌ 読みにくい
result = data.select{|x|x[:active]}.map{|x|x[:value]*2}.inject(:+)

# ✅ 読みやすい:処理ごとに改行する
result = data
  .select { |x| x[:active] }   # 有効なものだけ
  .map { |x| x[:value] * 2 }   # 2倍に変換
  .sum                           # 合計
  • selectmapsum / inject の順が基本(絞り込んでから変換・集計)
  • map してから select すると不要な変換が走るため、select を先に
  • group_bytransform_values はハッシュ集計の定番セット

まとめ

パターン チェーン
有効なものを集計する selectsum
有効なものを変換して集計する selectmapinject
タグ・ラベルを集計する flat_maptallysort_by
ランキングを作る selectsort_byfirst
グループ別に集計する group_bytransform_valuessort_by
カテゴリ別の最良を取る group_bytransform_values { max_by }

各メソッドの詳細は以下を参照してください。 - 「Ruby 配列の絞り込み:select・reject・find の使い方」 - 「Ruby 配列の変換・集計:map・inject・flat_map の使い方」 - 「Ruby ハッシュの繰り返し:each・map・select の使い方