JavaScriptでPython風のzip関数を実装する

JavaScriptzip関数がなかったので実装してみました。以下のように、各配列の同じインデックスの要素をまとめます。

const a1 = [1, 2, 3]
const a2 = ["Jan", "Feb", "Mar"]
const a3 = ["Garnet", "Amethyst", "Aquamarine"]

zip(a1, a2, a3)
#=> [[1, "Jan""Garnet"], [2, "Feb""Amethyst"],  [3, "Mar""Aquamarine"]]

各配列の長さが異なる場合には、一番短い配列の長さに切り詰められます。

const a1 = [1, 2, 3, 4]
const a2 = ["Jan", "Feb", "Mar", "Apr", "May"]
const a3 = ["Garnet", "Amethyst", "Aquamarine"]

zip(a1, a2, a3)
#=> [[1, "Jan""Garnet"], [2, "Feb""Amethyst"],  [3, "Mar""Aquamarine"]]

zip関数

zip関数の実装です。

const zip = (...arrays) => {
  const length = Math.min(...(arrays.map(arr => arr.length)))
  return new Array(length).fill().map((_, i) => arrays.map(arr => arr[i]))
}

コードの解説

まず、関数の定義の部分を解説します。レスト構文を使用して、引数全てをarraysに格納します。

const zip = (...arrays) => {

}

例えば、

const a1 = [1, 2, 3, 4]
const a2 = ["Jan", "Feb", "Mar", "Apr", "May"]
const a3 = ["Garnet", "Amethyst", "Aquamarine"]

zip(a1, a2, a3)

と呼び出したとき、arrays

[
  [1, 2, 3, 4],
  ["Jan", "Feb", "Mar", "Apr", "May"],
  ["Garnet", "Amethyst", "Aquamarine"]
]

です。以下、引数にこの配列を渡したときの動作を説明します。

次に、各配列の長さを求めます。

const zip = (...arrays) => {
  arrays.map(arr => arr.length)
  #=> [4, 5, 3]
}

この中の最小値は、Math.minとスプレッド演算子を使用して、以下のように書けます。

const zip = (...arrays) => {
  const length = Math.min(...(arrays.map(arr => arr.length)))
  #=> const length = Math.min(...[4, 5, 3])
  #=> const length = Math.min(4, 5, 3)
  #=> const length = 3
}

3回繰り返すので、new Array(length).fill().map((_, i) => i)の構文を使用します。

const zip = (...arrays) => {
  const length = Math.min(...(arrays.map(arr => arr.length)))
  new Array(length).fill().map((_, i) => i))
  #=> [0, 1, 2]
}

fillをはさむ理由については、こちらの記事をご覧ください。

yucatio.hatenablog.com

各配列のi番目の要素は、

arrays.map(arr => arr[i])

で取得することができます。よくわからない場合は、iではなく、0、1、2など具体的な数字で考えるとよいです。例えば、各配列の0番目の要素は、

arrays.map(arr => arr[0])
#=> [1, "Jan""Garnet"]

です。

これをmapに渡す関数の戻り値にします。

const zip = (...arrays) => {
  const length = Math.min(...(arrays.map(arr => arr.length)))
  return new Array(length).fill().map((_, i) => arrays.map(arr => arr[i]))
  #=> [[1, "Jan""Garnet"], [2, "Feb""Amethyst"],  [3, "Mar""Aquamarine"]]
}

以上でzip関数の完成です。

zip_longest

Pythonzip_longestにあたる関数はMath.minの部分をMath.maxにするだけです。 こちらの記事に詳しい動作を載せています。

yucatio.hatenablog.com

イテレータの使用

Pythonではイテレータを使用して配列の要素にアクセスしています。list[i]のようにインデックスを指定してアクセスするよりも、イテレータの方が速いことが期待されるからでしょう。 (参考: 組み込み関数 — Python 3.8.1 ドキュメント )

JavaScriptにもイテレータがあるので そちらを利用しようかと思いましたが、あまり慣れている書き方ではないので今回は見送りました。 ( 参考:Array.prototype.values() - JavaScript | MDN)

rubyのzipメソッド

rubyにもzipメソッドがあり、似た動きをするのですが、こちらはArrayクラスのインスタンスメソッドです。こちらはレシーバの要素数が作成後の配列の長さになります。

Array#zip (Ruby 2.7.0 リファレンスマニュアル)

[1,2,3].zip([4,5,6], [7,8,9])
# => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]