LoginSignup
36
31

More than 5 years have passed since last update.

select + map するならinject / reduceよりもeach_with_object

Last updated at Posted at 2016-09-01

要約

コレクションに対して

  1. 絞り込みと
  2. データ処理

をしたいときはeach_with_objectを使うと良いという話です。

詳細

Effective Rubyを読む中で、よく書きがちな以下のコードに対してinject / reduceを用いよという教えが書かれていました。


# こんなデータがあるとする。
User = Struct.new(:name, :age)
users = [
  User.new('user1', 21),
  User.new('user2', 19),
  User.new('user3', 23),
  User.new('user4', 35),
  User.new('user5', 10),
  User.new('user6', 18),
  User.new('user7', 24),
  User.new('user8', 15),
  User.new('user9', 21),
]

# 成人だけを絞り込んで、その名前リストが欲しい。そんなときは...

# これよりも
users.select { |u| u.age >= 20 }.map(&:name)

# これがいい(パフォーマンス的に。)
users.inject([]) do |result, user|
  result << user.name if user.age > 20
  result # injectのブロックは必ずnil以外を返そう。
end

「お、なるほど」「injectの引数に空配列か〜」と勉強になったなと思いながら、そのようなコードを書いたところ、rubocopがUse `each_with_object` instead of `inject`.とメッセージを発していました。

何だこれと思い調べてみると、リファレンスには以下のように説明が。

each_with_indexメソッドは、要素を使って何らかのオブジェクトを操作するのに使います。要素の数だけブロックを繰り返し実行し、繰り返しごとにブロック引数itemには各要素を、memoには引数objectで指定したオブジェクトを入れます。戻り値は、objectのオブジェクトです。

  • each_with_objectの引数をブロック引数に持ち
  • その引数をメモとして更新しつつ、各要素ごとにブロックを回す
  • なので、ブロックの返り値を明示的に書かなくていい
    • 返り値を次のブロック引数として渡すわけではないので

といった使い方で、select + map目的であればeach_with_objectを使ったほうが用途に適切なコードになるとのこと。

書き換え後がこちら

users.each_with_object([]) do |user, result|
  result << user.name if user.age > 20
end

冒頭の目的であればeach_with_objectを使うべきというtipsでした。

36
31
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
31