【ruby】ループ処理いろいろ
最近rubyのコードを触る中でいろんなループ処理を捌く機会があったので備忘録として書き留めておきます。
今回の記事で整理しているループ処理のメソッドは
each
map
inject
each_with_object
の4つです。
ループ処理は決してこれだけではないので、用途に合わない場合はいろいろ調べてみてください。
それでは早速見ていきます。
each
とmap
each
とmap
で同じコードを書くと以下のような違いが出ます。
pry(main)> [1, 2, 3, 4, 5].each do |elm| pry(main)* elm * 2 pry(main)* end => [1, 2, 3, 4, 5]
pry(main)> [1, 2, 3, 4, 5].map do |elm| pry(main)* elm * 2 pry(main)* end => [2, 4, 6, 8, 10]
each
では、ループ処理の中身に関係なく受け取った値をそのまま返します。
一方map
では、ループ処理の中の最後の評価値を配列の要素に代入した値を返します。
map
の場合「最後の評価値」ということですが、例えば処理の最後にputs
を使用していた場合は以下のようにnil
が入ることになります。(puts
は値を表示後nil
を返すため。)
pry(main)> [1, 2, 3, 4, 5].map do |elm| pry(main)* puts (elm * 2) pry(main)* end 2 4 6 8 10 => [nil, nil, nil, nil, nil]
これもeach
であれば最後の処理に関係なく受け取った配列と同じ配列が返ってきます。
pry(main)> [1, 2, 3, 4, 5].each do |elm| pry(main)* puts (elm * 2) pry(main)* end 2 4 6 8 10 => [1, 2, 3, 4, 5]
まとめると、
each
→ 与えたデータを単に参照したいだけの時(データに変更を加える必要のない時)map
→ 与えたデータにをもとに何らかの新しいデータを取得したい時
のように使い分けると便利です。
inject
とeach_with_object
inject
inject
では複数の変数を与えることで簡単に配列の計算などを行うことができます。
まずeach
を使った例を見てみます。
pry(main)> sum = 0 => 0 pry(main)> [1,2,3,4,5].each do |n| pry(main)* sum = sum + n pry(main)* end => [1, 2, 3, 4, 5] pry(main)> sum => 15
each
を使うとループの外でsum
を初期化するなど長くなってしまう計算も、inject
を使うことでシンプルにループの中で完結させることができます。
pry(main)> [1,2,3,4,5].inject do |sum, n| pry(main)* sum + n pry(main)* end => 15
さてこのinject
の繰り返し処理ですが、パイプ文字sum
とn
はそれぞれ以下のように値を受け取っています。
pry(main)> [1,2,3,4,5].inject do |sum, n| pry(main)* puts "sum = #{sum}" pry(main)* puts "n = #{n}" pry(main)* sum + n pry(main)* end sum = 1 n = 2 sum = 3 n = 3 sum = 6 n = 4 sum = 10 n = 5 => 15
1回目のループではsum
が初期値として配列の最初の値(1
)を受け取り、n
は二つ目の値(2
)を受け取ります。そして2回目以降のループになるとsum
は前回のループの最後の評価値(sum + n
)を受け取り、n
は順次配列の3つ目以降の値を受け取っていくという流れになります。
さらに、inject(初期値)
とすることで以下のようにsum
の初期値を指定することもできます。
pry(main)> [1,2,3,4,5].inject(0) do |sum, n| pry(main)* puts "sum = #{sum}" pry(main)* puts "n = #{n}" pry(main)* sum + n pry(main)* end sum = 0 n = 1 sum = 1 n = 2 sum = 3 n = 3 sum = 6 n = 4 sum = 10 n = 5 => 15
この場合、n
は1回目のループで配列の一つ目の値を受け取ることになります。
またinject
の引数にハッシュや配列を受け取ることも可能です。
pry(main)> [1,2,3,4,5].inject([]) do |array, n| pry(main)* array.push(n) pry(main)* end => [1, 2, 3, 4, 5]
しかしmap
同様2回目以降のループでは前回のループの最後の評価値が参照されるので使い方に注意が必要です。
each_with_object
ループのなかで任意のオブジェクトを作成したい時はeach_with_object
が便利です。
people = [ { id: 1, name: "tanaka", age: 21, home_town: "Tokyo"}, { id: 2, name: "suzuki", age: 30, home_town: "Osaka"}, { id: 3, name: "sato", age: 26, home_town: "Fukuoka"} ]
例として上のような人物情報から人の名前と年齢だけを抜き出したリストage_list
を作りたいときを考えてみます。
map
だと、
pry(main)> age_list = {} => {} pry(main)> people.map do |person| pry(main)* age_list[person[:name]] = person[:age] pry(main)* end => [21, 30, 26] pry(main)> age_list => {"tanaka"=>21, "suzuki"=>30, "sato"=>26}
となりループの外でハッシュの初期化や参照をしなければなりません。
これをinject
で扱うと以下のようにmap
より簡単に書けます。
pry(main)> age_list = people.inject({}) do |hash, person| pry(main)* hash[person[:name]] = person[:age] pry(main)* hash pry(main)* end => {"tanaka"=>21, "suzuki"=>30, "sato"=>26}
しかしinject
の2回目以降のループで変数hash
はループの中の最後の評価値を受け取るためループの最後にhash
を与えなければなりません。
ここでeach_with_object
ではループ処理とオブジェクトの初期化を同時に可能であるため、
pry(main)> age_list = people.each_with_object({}) do |person, hash| pry(main)* hash[person[:name]] = person[:age] pry(main)* end => {"tanaka"=>21, "suzuki"=>30, "sato"=>26}
となりmap
やinject
より簡潔に書くことができます。
inject
とeach_with_object
似てるところが多いですが、以下のような違いがあります。
それぞれ
hoge.inject(object) do |one, two| hoge.each_with_object(object) do |one, two|
とした場合、
inject
は一つ目のパイプ文字(one
)で、each_with_object
は二つ目のパイプ文字(two
)でそれぞれ引数(object
)を参照するinject
→ 1周目:one
=object
, 2周目以降:one
=前回の最終評価値each_with_object
→ 1周目:two
=object
, 2周目以降:two
=two
となります。
まとめ
使い分けがややこしいループ系メソッドの違いが理解できたでしょうか。
今回は備忘録記事になりましたが、少しでもメソッド理解の役に立てていれば幸いです。
(間違いや誤りがあればご指摘お願いします!)