悩み
以下のコードを見てください。
array_1 = [1, 2, 3, 4, 5]
array_2 = ['a', 'b', 'c', 'd', 'e']
array_3 = ['A', 'B', 'C', 'D', 'E']
array_1.each_with_index do |v1, i|
v2 = array_2[i]
v3 = array_3[i]
pp "#{v1}, #{v2}, #{v3}"
end
このコード、以下の部分がなんとなく嫌じゃないですか?
v2 = array_2[i]
v3 = array_3[i]
なにかうまく書く方法がありそうな気がします。
今回はそんな悩みを解決する関数を見つけたので共有したいと思います。
結論
以下のように Array#zip
を利用しようって話です。
※ただし、大容量の配列の場合はパフォーマンスが悪いので注意。
array_1 = [1, 2, 3, 4, 5]
array_2 = ['a', 'b', 'c', 'd', 'e']
array_3 = ['A', 'B', 'C', 'D', 'E']
array_1.zip(array_2, array_3).each do |v1, v2, v3|
pp "#{v1}, #{v2}, #{v3}"
end
解説
Array#zip
は複数の配列の同じ要素番号を持つもので配列を作成し、それを二次元配列にします。
言葉で説明するのはとても難しいのですが、以下のサンプルコードを見てもらえるとわかりやすいと思います。
array_1 = ['A', 'B']
array_2 = ['a', 'b']
pp array_1.zip(array_2)
=> [['A', 'a'],['B', 'b']]
この関数を利用することで悩み
のコードを以下のように記述できます。
array_1 = [1, 2, 3, 4, 5]
array_2 = ['a', 'b', 'c', 'd', 'e']
array_3 = ['A', 'B', 'C', 'D', 'E']
array_1.zip(array_2, array_3).each do |array_zip|
v1 = array_zip[0]
v2 = array_zip[1]
v3 = array_zip[2]
pp "#{v1}, #{v2}, #{v3}"
end
このままでv1,v2,v3
変数に代入しているところが冗長なので以下のように分割代入を行い完成です。
array_1 = [1, 2, 3, 4, 5]
array_2 = ['a', 'b', 'c', 'd', 'e']
array_3 = ['A', 'B', 'C', 'D', 'E']
# ブロック引数に分割代入
array_1.zip(array_2, array_3).each do |v1, v2, v3|
pp "#{v1}, #{v2}, #{v3}"
end
少しわかりにくいですが、ブロック引数が増えたのは単純に配列を分割代入していただけで特殊な構文を利用したわけではないです。
ちなみにeach_with_index
を利用すると以下のようになります。
array_1 = [1, 2, 3, 4, 5]
array_2 = ['a', 'b', 'c', 'd', 'e']
array_3 = ['A', 'B', 'C', 'D', 'E']
# ブロック引数の第一引数に分割代入
array_1.zip(array_2, array_3).each_with_index do |(v1, v2, v3), i|
pp "#{v1}, #{v2}, #{v3}, #{i}"
end
パフォーマンスは?
実際に試してみます。
インデックスを利用してループしたやり方
x = 1000000
start_time = Time.now()
array_1 = (1..x).to_a
array_2 = (1..x).to_a
array_3 = (1..x).to_a
array_1.each_with_index do |v1, i|
v2 = array_2[i]
v3 = array_3[i]
"#{v1}, #{v2}, #{v3}"
end
pp "記録:#{Time.now() - start_time}"
記録:0.374375秒
Array#zipを利用したやり方
x = 1000000
start_time = Time.now()
array_1 = (1..x).to_a
array_2 = (1..x).to_a
array_3 = (1..x).to_a
array_1.zip(array_2, array_3).each do |v1, v2, v3|
"#{v1}, #{v2}, #{v3}"
end
pp "記録:#{Time.now() - start_time}"
記録:0.480742秒
結果からわかるようにArray#zip関数を利用することで、スッキリ書くことはできますが、結果に対して無駄なステップを踏んでいる分遅くなります。
Githubを見るとArray#zipの実態はC言語でコンパイルしたファイルを実行しているので、ある程度の速度は出ますが、大容量の配列に対してArray#zip関数を利用するのは控える方が良いでしょう。
ちなみにx = 1000000
の部分を10倍にしてx = 10000000
にすると
インデックスを利用してループしたやり方
が4.089633秒
だったのに対しArray#zipを利用したやり方
は8.594169秒
になりました。この結果からデータ数が増えれば増えるほど比率が悪化することが予想されるので気をつけましょう。
参考