KaQiita

新米社会人が適当なことを書いてます。温かく見守ってやってください。

【Ruby】inject・reduce を使ってリファクタリングする

はじめに

Ruby には、畳み込み演算を行う Enumerable モジュールのメソッドとして injectreduce というのがあります。

詳しい挙動はこちらを見ていただけると良いと思います。

docs.ruby-lang.org

これらのメソッドはとても便利で、これらを使えば綺麗に書ける場合が多々あります。

しかし少し挙動が分かりにくいため、これらが使えると気付かずに冗長に書いてしまうことが良くあるなと最近思ったので、injectreduce の活用例をまとめておこうと思います。

活用例

商品の注文 order のうち飲み物の注文(order.type == 'drink')の金額 order.fee の合計額を出したい。

【書いてしまいがちなコード】

def sum_of_drink_order
  sum = 0
  orders.each do |order|
    sum += order.fee if order.type == 'drink'
  end
  sum
end

injectreduce を使うと...】

def sum_of_drink_order
  orders.inject(0) do |sum, order|
    sum + order.fee if order.type == 'drink'
  end
end

ある日にアクセスされた記事の ID がアクセスされた回数分ダブって入っている配列 accessed_article_ids を用いて、各記事のアクセス数のデータ({ '記事ID' => 'アクセス数' })を作りたい。

【書いてしまいがちなコード】

def article_access_count
  hash = Hash.new(0)
  accessed_article_ids.each do |accessed_article_id|
    hash[accessed_article_id] += 1
  end
  hash
end

injectreduce を使うと...】

def article_access_count
  accessed_article_ids.reduce(Hash.new(0)) do |hash, accessed_article_id|
    hash[accessed_article_id] += 1
    hash
  end
end

アカウント account の中から累計ログイン回数 account.login_count が 10 回以上のアカウントのメールアドレス account.email を配列で取得したい。

【書いてしまいがちなコード】

accounts.select{ |account| account.login_count >= 10 }.map(&:email)

injectreduce を使うと...】

accounts.reduce([]) do |array, account|
  array << account.email if account.login_count >= 10
  array
end

終わりに

これらの活用例のように、injectreduce を使うことで分かりやすいコード・効率的なコードを書くことができます。

特に3つ目の活用例は、一見複雑になっているように見えるかもしれませんが、複数の配列を作る必要のない効率的なコードだと思います。

eachmapselect などの分かりやすい Enumerable モジュールのメソッドを良く考えずに使ってしまいがちですが、injectreduce も結構使う場面あるよ、という話でした。

参考文献