🧝♀️
【Ruby】each_with_objectの使い方
each_with_object
は便利だがあまり知られていなさそう、かつ個人的には少し理解するのに時間がかかったので使い方をまとめた。
まずは公式ドキュメント見ようぜのURLを貼っておく
早速、こんな時は each_with_object
使えるぜ!!な例を上げていく
例1
こんな時、
ruby
langs = ["ruby", "python", "go"]
lang_hash = {}
langs.each do |lang|
lang_hash[lang] = lang.upcase
end
return lang_hash
#=> {"ruby"=>"RUBY", "python"=>"PYTHON", "go"=>"GO"}
each_with_object
を使うと
ruby
langs = ["ruby", "python", "go"]
lang_hash = langs.each_with_object({}) do |lang, hash|
hash[lang] = lang.upcase
end
return lang_hash
#=> {"ruby"=>"RUBY", "python"=>"PYTHON", "go"=>"GO"}
lang_hash = {}
が不要になる。
例2
こんな時、
ruby
posts = [
{ id: 1, title: 'hoge-1', category: 1 },
{ id: 2, title: 'hoge-2', category: 2 },
{ id: 3, title: 'hoge-3', category: 2 },
{ id: 4, title: 'hoge-4', category: 1 },
]
# hashの初期化
post_hash_by_category_id = {}
posts.each do |post|
# categoryごとの配列初期化
post_hash_by_category_id[post[:category]] = [] if post_hash_by_category_id[post[:category]].nil?
post_hash_by_category_id[post[:category]] << { title: post[:title] }
end
return post_hash_by_category_id
#=> {1=>[{:title=>"hoge-1"}, {:title=>"hoge-4"}], 2=>[{:title=>"hoge-2"}, {:title=>"hoge-3"}]}
これを each_with_object
で実装すると、
ruby
posts = [
{ id: 1, title: 'hoge-1', category: 1 },
{ id: 2, title: 'hoge-2', category: 2 },
{ id: 3, title: 'hoge-3', category: 2 },
{ id: 4, title: 'hoge-4', category: 1 },
]
post_hash_by_category_id = posts.each_with_object(Hash.new { |v, k| v[k] = []}) do |post, hash|
hash[post[:category]] << { title: post[:title] }
end
return post_hash_by_category_id
#=> {1=>[{:title=>"hoge-1"}, {:title=>"hoge-4"}], 2=>[{:title=>"hoge-2"}, {:title=>"hoge-3"}]}
# hashの初期化
post_hash_by_category_id = {}
と
# categoryごとの配列初期化
post_hash_by_category_id[post[:category]] = [] if post_hash_by_category_id[post[:category]].nil?
が不要になる。
ぜひ、手を動かして、実務でリファクタリングしてみてください。
余談1
これを書くきっかけが、実は レビューで Hash.new { |v, k| v[k] = []})
使うといいよーって言われて、どんなふうに使うと良くなるかなーと考えて色々調べたら each_with_object
というメソッドを使うといいらしいというところに行き着いた。(例2のパターン)
余談2
公式ドキュメントは使い方としてはいい例かもしれないが、用途としてはダメな例。
まーみればわかる通り、これ map
使った方がええやろーって感じなので、ドキュメントで書かれてるから、これで!!とはしないように・・・
each_with_object
とmap
を使った時の比較
evens = (1..10).each_with_object([]) {|i, a| a << i*2 }
evens = (1..10).map{ |i| i*2 }
Discussion
tsubasa_ryuto さん、こんにちは。
each_with_object
、僕の場合、昔のRubyではよく使ってたんですが、最近は便利メソッドが増えてきてあまり使わなくなってきましたね。。例1 のようなケースは
to_h
メソッドを使った方がシンプルになります(Ruby 2.6以上なら)。例2 はたしかに
each_with_object
が便利ですが、一方でeach_with_object(Hash.new { |v, k| v[k] = []})
の部分がごちゃごちゃしてあまり可読性が高くない気がします。僕だったらあえてただのループにするかもしれません。
なお、上のコードでは
{ title: post[:title] }
の代わりにslice
メソッドを使ってみました。以上、ご参考までに😃
ありがとうございます!!
参考にさせていただきます😄