JavaScriptでPython風のzip関数を実装する
JavaScriptにzip
関数がなかったので実装してみました。以下のように、各配列の同じインデックスの要素をまとめます。
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
をはさむ理由については、こちらの記事をご覧ください。
各配列の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
Pythonのzip_longest
にあたる関数はMath.min
の部分をMath.max
にするだけです。
こちらの記事に詳しい動作を載せています。
イテレータの使用
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]]