Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
31
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

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

要約

コレクションに対して

  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でした。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
31
Help us understand the problem. What are the problem?