1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby each_with_object 備忘録

1
Posted at

背景

  • コードリーディング中に each_with_object というメソッドを見かけることが多い
  • すごく便利なのはわかってるんだけど、毎回何これ?ってなるのでまとめといた

each_with_object とは

Enumerable#each_with_object は、Ruby 1.9 で追加された組み込みメソッド。「初期値となるオブジェクト(ハッシュや配列)を用意して、ループしながら中に詰めていき、最後にそのオブジェクトを返す」という一連の処理を1メソッドで行える

基本構文

collection.each_with_object(初期オブジェクト) do |要素, オブジェクト|
  # オブジェクトに要素を詰める処理
end
# → 完成したオブジェクトが戻り値になる
  • 第1引数の 初期オブジェクト が、ブロックの第2引数 オブジェクト として毎回渡される
  • ブロック内で オブジェクト を破壊的に変更していく
  • ループ終了後、完成した オブジェクト が自動的に戻り値になる

each との比較

each で書く場合(3ステップ必要)

hash = {}                          # 1. 箱を用意
fruits.each do |fruit|
  hash[fruit] = fruit.length      # 2. ループで詰める
end
hash                               # 3. 箱を返す

変数の初期化・ループ処理・戻り値の返却が分散しており、特にメソッドの途中に書くと「この hash はどこで使われるんだ?」と読みづらくなる

each_with_object で書く場合(1ステップに集約)

fruits.each_with_object({}) do |fruit, hash|
  hash[fruit] = fruit.length
end

初期化から戻り値まで1つの式にまとまるため、意図が明確になる。一時変数の宣言も不要

ループの流れを1回ずつ追う

理解のために、1ループずつ状態を書き出してみる

fruits = ["りんご", "みかん", "ぶどう"]
fruits.each_with_object({}) do |fruit, hash|
  hash[fruit] = fruit.length
end
開始前:  hash = {}
1回目:   fruit="りんご" → hash["りんご"]=3  → {"りんご"=>3}
2回目:   fruit="みかん" → hash["みかん"]=3  → {"りんご"=>3, "みかん"=>3}
3回目:   fruit="ぶどう" → hash["ぶどう"]=3  → {"りんご"=>3, "みかん"=>3, "ぶどう"=>3}
終了:    完成したhashが戻り値として返る

ポイントは hash が毎回同じオブジェクトであること。fruit だけがループごとに変わり、hash には前回までの結果が蓄積されていく

実践例:URLからIDを抽出してハッシュを構築する

実際のRailsプロジェクトで使われていたパターン(プロジェクト名等はサンプルに差し替え)

projects.each_with_object({}) do |project, hash|
  id = URI.parse(project.url).path.split('/').last
  next unless id.match?(/\A\d+\z/)
  hash[id.to_i] = project
end

結果のイメージ:

{ 123 => <Project A>, 456 => <Project B>, 789 => <Project C> }

このハッシュがあれば、IDで一発検索できる:

hash[456]  # → <Project B>

コード中で使われているメソッド

  • URI.parse(url).path — URLからパス部分(例: /projects/456)を取り出す。URI は Ruby 標準ライブラリ
  • split('/') — 文字列を指定した区切り文字で分割して配列にする。String#split
  • last — 配列の最後の要素を取得する。Array#last
  • match?(/\A\d+\z/) — 正規表現で文字列全体が数字のみかをチェックする。\A は文字列の先頭、\d+ は1文字以上の数字、\z は文字列の末尾。String#match? はマッチすれば true を返す

感想

  • 便利なんだけど、毎回忘れるんだよなぁ

参考

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?