5
2

複数の配列をまとめるArray#zip関数を使ってコードをスッキリさせる

Posted at

悩み

以下のコードを見てください。

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秒になりました。この結果からデータ数が増えれば増えるほど比率が悪化することが予想されるので気をつけましょう。

参考

5
2
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
5
2