Javascript setara dengan fungsi zip Python


215

Apakah ada fungsi javascript yang setara dengan fungsi zip Python? Artinya, diberikan beberapa array dengan panjang yang sama membuat array pasangan.

Misalnya, jika saya memiliki tiga array yang terlihat seperti ini:

var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
var array3 = [4, 5, 6];

Array output harus:

var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]

5
Apakah adil untuk mengatakan bahwa kita programmer Python 'takut' terhadap metode bodoh yang melibatkan loop karena mereka lambat, dan karenanya selalu mencari metode bawaan untuk melakukan sesuatu. Tapi itu di Javascript kita harus melanjutkan dan menulis loop kami karena mereka tidak terlalu lambat?
LondonRob

3
@ LondonRob Loop adalah loop, tersembunyi di balik metode 'cepat' atau tidak. JavaScript telah pasti telah mendapatkan lebih banyak dukungan untuk fungsi-fungsi yang lebih tinggi, dengan pengenalan Array ini forEach, reduce, map, every, dll Itu hanya kasus yang ziptidak "membuat cut" (a flatMapjuga absen), bukan untuk pertimbangan kinerja - tapi Agar adil, .NET (3.5) tidak memiliki Zip pada Enumerable selama beberapa tahun! Pustaka 'fungsionalis' seperti garis bawah / lodash (lodash 3.x memiliki evaluasi urutan malas) akan menyediakan fungsi zip yang setara.
user2864740

@ user2864740 Loop yang ditafsirkan (seperti dalam Python) akan selalu jauh lebih lambat daripada loop kode mesin. Loop yang dikompilasi dengan JIT (seperti pada mesin JS modern) dapat mendekati kecepatan CPU asli, sedemikian rupa sehingga penguatan yang diperkenalkan dengan menggunakan loop kode mesin dapat diimbangi dengan overhead panggilan fungsi anonim. Namun, masuk akal untuk memiliki fungsi bawaan ini dan membuat profil beberapa variasi "loop batin" Anda dengan beberapa mesin JS. Hasilnya mungkin tidak jelas.
Tobia

Jawaban:


177

Pembaruan 2016:

Ini versi Ecmascript 6 yang lebih keren:

zip= rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))

Ilustrasi equiv. ke Python { zip(*args)}:

> zip([['row0col0', 'row0col1', 'row0col2'],
       ['row1col0', 'row1col1', 'row1col2']]);
[["row0col0","row1col0"],
 ["row0col1","row1col1"],
 ["row0col2","row1col2"]]

(dan FizzyTea menunjukkan bahwa ES6 memiliki sintaks argumen variadik, sehingga definisi fungsi berikut akan bertindak seperti python, tetapi lihat di bawah untuk penafian ... ini tidak akan menjadi kebalikannya sendiri sehingga zip(zip(x))tidak akan sama x; meskipun seperti yang ditunjukkan Matt Kramer zip(...zip(...x))==x(seperti dalam python biasazip(*zip(*x))==x ))

Definisi definisi alternatif. ke Python { zip}:

> zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]))
> zip( ['row0col0', 'row0col1', 'row0col2'] ,
       ['row1col0', 'row1col1', 'row1col2'] );
             // note zip(row0,row1), not zip(matrix)
same answer as above

(Harap perhatikan bahwa ...sintaksis mungkin memiliki masalah kinerja saat ini, dan mungkin di masa depan, jadi jika Anda menggunakan jawaban kedua dengan argumen variadik, Anda mungkin ingin mengujinya.)


Inilah oneliner:

function zip(arrays) {
    return arrays[0].map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

// > zip([[1,2],[11,22],[111,222]])
// [[1,11,111],[2,22,222]]]

// If you believe the following is a valid return value:
//   > zip([])
//   []
// then you can special-case it, or just do
//  return arrays.length==0 ? [] : arrays[0].map(...)

Di atas mengasumsikan bahwa array memiliki ukuran yang sama, sebagaimana mestinya. Ini juga mengasumsikan Anda memasukkan dalam daftar argumen daftar, tidak seperti versi Python di mana daftar argumen variadic.Jika Anda ingin semua "fitur" ini, lihat di bawah. Hanya dibutuhkan sekitar 2 baris kode tambahan.

Berikut ini akan meniru zipperilaku Python pada kasus tepi di mana array tidak memiliki ukuran yang sama, diam-diam berpura-pura bahwa bagian array yang lebih lama tidak ada:

function zip() {
    var args = [].slice.call(arguments);
    var shortest = args.length==0 ? [] : args.reduce(function(a,b){
        return a.length<b.length ? a : b
    });

    return shortest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222]]]

// > zip()
// []

Ini akan meniru itertools.zip_longestperilaku Python , menyisipkan di undefinedmana array tidak didefinisikan:

function zip() {
    var args = [].slice.call(arguments);
    var longest = args.reduce(function(a,b){
        return a.length>b.length ? a : b
    }, []);

    return longest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222],[null,null,333]]

// > zip()
// []

Jika Anda menggunakan dua versi terakhir ini (variadic alias. Versi multi-argumen), maka zip tidak lagi kebalikannya sendiri. Untuk meniru zip(*[...])idiom dari Python, Anda harus melakukan zip.apply(this, [...])ketika Anda ingin membalikkan fungsi zip atau jika Anda ingin memiliki jumlah daftar variabel yang sama sebagai input.


tambahan :

Untuk membuat ini menangani semua iterable (misalnya dalam Python Anda dapat menggunakan zippada string, rentang, objek peta, dll.), Anda bisa menentukan yang berikut:

function iterView(iterable) {
    // returns an array equivalent to the iterable
}

Namun jika Anda menulis zipdengan cara berikut , itu pun tidak perlu:

function zip(arrays) {
    return Array.apply(null,Array(arrays[0].length)).map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

Demo:

> JSON.stringify( zip(['abcde',[1,2,3,4,5]]) )
[["a",1],["b",2],["c",3],["d",4],["e",5]]

(Atau Anda dapat menggunakan range(...)fungsi gaya-Python jika Anda sudah menulisnya. Akhirnya Anda akan dapat menggunakan ECMAScript array array atau generator.)


1
Ini tidak berfungsi untuk saya: TypeError: Object 1 tidak memiliki metode 'peta'
Emanuele Paolini

7
Dan ES6 untuk argumen variadic dan semua iterable:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983

"Objek 1 tidak memiliki metode 'peta'" mungkin adalah kasus mencoba menggunakan ini pada objek yang tidak memiliki metode peta (seperti nodelist, atau string) yang dicakup dalam lampiran posting ini
ninjagecko

Meskipun benar bahwa versi ES6 variadic tidak diawetkan zip(zip(x)) = x, Anda masih dapat menikmati itu zip(...zip(...x)) = x.
Matt Kramer

const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
Константин Ван

34

Lihat perpustakaan Garis Bawah .

Underscore menyediakan lebih dari 100 fungsi yang mendukung pembantu fungsional hari kerja favorit Anda: peta, filter, aktifkan - juga barang-barang yang lebih terspesialisasi: penjilidan fungsi, templating javascript, membuat indeks cepat, pengujian kesetaraan yang dalam, dan sebagainya.

- Katakan orang yang membuatnya

Saya baru-baru ini mulai menggunakannya khusus untuk zip()fungsi dan telah meninggalkan kesan pertama yang bagus. Saya menggunakan jQuery dan CoffeeScript, dan itu hanya berjalan dengan mereka. Garis bawah mengambil tepat di tempat mereka pergi dan sejauh ini tidak mengecewakan saya. Oh, omong-omong, itu hanya 3kb yang diperkecil.

Saksikan berikut ini:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]

3
Saat menggunakan Underscore, Anda merasa sedikit lebih dekat dengan kejelasan dan kesenangan logis dari Haskell.
CamilB

12
Alih-alih menggarisbawahi, coba ini: lodash.com - pengganti drop-in, rasa yang sama, lebih banyak fitur, lebih banyak konsistensi lintas-browser, perf yang lebih baik. Lihat kitcambridge.be/blog/say-hello-to-lo-dash untuk keterangan.
Merlyn Morgan-Graham

16

Selain jawaban ninjagecko yang luar biasa dan komprehensif, yang diperlukan untuk meng-zip dua array-JS menjadi "tuple-mimic" adalah:

//Arrays: aIn, aOut
Array.prototype.map.call( aIn, function(e,i){return [e, aOut[i]];})

Penjelasan:
Karena Javascript tidak memiliki tuplestipe, fungsi untuk tupel, daftar, dan set bukanlah prioritas utama dalam spesifikasi bahasa.
Jika tidak, perilaku serupa dapat diakses secara langsung melalui peta Array di JS> 1.6 . ( mapsebenarnya sering diterapkan oleh pembuat mesin JS di banyak> mesin JS 1.4, meskipun tidak ditentukan).
Perbedaan utama Python zip, izip, ... hasil dari mapgaya fungsional 's, karena mapmemerlukan fungsi-argumen. Selain itu itu adalah fungsi dari Array-instance. Satu dapat menggunakan Array.prototype.mapsebagai gantinya, jika deklarasi tambahan untuk input adalah masalah.

Contoh:

_tarrin = [0..constructor, function(){}, false, undefined, '', 100, 123.324,
         2343243243242343242354365476453654625345345, 'sdf23423dsfsdf',
         'sdf2324.234dfs','234,234fsf','100,100','100.100']
_parseInt = function(i){return parseInt(i);}
_tarrout = _tarrin.map(_parseInt)
_tarrin.map(function(e,i,a){return [e, _tarrout[i]]})

Hasil:

//'('+_tarrin.map(function(e,i,a){return [e, _tarrout[i]]}).join('),\n(')+')'
>>
(function Number() { [native code] },NaN),
(function (){},NaN),
(false,NaN),
(,NaN),
(,NaN),
(100,100),
(123.324,123),
(2.3432432432423434e+42,2),
(sdf23423dsfsdf,NaN),
(sdf2324.234dfs,NaN),
(234,234fsf,234),
(100,100,100),
(100.100,100)

Kinerja terkait:

Menggunakan mapover- forloop:

Lihat: Apa cara penggabungan yang paling efisien [1,2] dan [7,8] menjadi [[1,7], [2,8]]

tes zip

Catatan: tipe dasar seperti falsedan undefinedtidak memiliki hierarki objek prototipal dan karenanya tidak memaparkan toStringfungsi. Karenanya ini ditampilkan sebagai kosong dalam output.
Karena parseIntargumen kedua adalah basis / angka radix, yang digunakan untuk mengkonversi angka, dan karena mapmeneruskan indeks sebagai argumen kedua ke fungsi argumennya, fungsi wrapper digunakan.


Contoh pertama Anda mengatakan "aIn not a function" ketika saya mencobanya. Ini berfungsi jika saya memanggil .map dari array bukan sebagai prototipe: aIn.map(function(e, i) {return [e, aOut[i]];})Apa yang salah?
Noumenon

1
@ Noumenon, Array.prototype.mapseharusnya sudah Array.prototype.map.call, tetap jawabannya.
pengguna

11

Contoh ES6 modern dengan generator:

function *zip (...iterables){
    let iterators = iterables.map(i => i[Symbol.iterator]() )
    while (true) {
        let results = iterators.map(iter => iter.next() )
        if (results.some(res => res.done) ) return
        else yield results.map(res => res.value )
    }
}

Pertama, kita mendapatkan daftar iterables sebagai iterators. Ini biasanya terjadi secara transparan, tetapi di sini kita melakukannya secara eksplisit, ketika kita menghasilkan langkah demi langkah sampai salah satu dari mereka kelelahan. Kami memeriksa apakah ada hasil (menggunakan .some()metode) dalam array yang diberikan telah habis, dan jika demikian, kami mematahkan loop while.


Jawaban ini bisa menggunakan lebih banyak penjelasan.
cmaher

1
Kami mendapatkan daftar iterator dari iterables. Ini biasanya terjadi secara transparan, di sini kita melakukannya secara eksplisit, ketika kita menghasilkan langkah demi langkah sampai salah satu dari mereka kelelahan. Periksa apakah ada di antara mereka (metode .some ()) dalam array sudah habis dan kami menyela jika benar.
Dimitris

11

Di samping fungsi-fungsi lain yang mirip dengan Python, pythonicmenawarkan sebuah zipfungsi, dengan manfaat tambahan untuk mengembalikan malas yang dievaluasi Iterator, mirip dengan perilaku rekan Python-nya :

import {zip, zipLongest} from 'pythonic';

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd', 'e'];
for (const [first, second] of zip(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d

for (const [first, second] of zipLongest(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d
// first: undefined, second: e

// unzip
const [arrayFirst, arraySecond] = [...zip(...zip(arr1, arr2))];

Pengungkapan Saya adalah penulis dan pengelola Pythonic


7

Python memiliki dua fungsi: zip dan itertools.zip_longest. Implementasi pada JS / ES6 adalah seperti ini:

Implementasi zip Python`s di JS / ES6

const zip = (...arrays) => {
    const length = Math.min(...arrays.map(arr => arr.length));
    return Array.from({ length }, (value, index) => arrays.map((array => array[index])));
};

Hasil:

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    [11, 221]
));

[[1, 667, 111, 11]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111, 212, 323, 433, '1111']
));

[[1, 667, 111], [2, false, 212], [3, -378, 323], ['a', '337', 433]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[]

Implementasi zip_longest Python`s di JS / ES6

( https://docs.python.org/3.5/library/itertools.html?highlight=zip_longest#itertools.zip_longest )

const zipLongest = (placeholder = undefined, ...arrays) => {
    const length = Math.max(...arrays.map(arr => arr.length));
    return Array.from(
        { length }, (value, index) => arrays.map(
            array => array.length - 1 >= index ? array[index] : placeholder
        )
    );
};

Hasil:

console.log(zipLongest(
    undefined,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, tidak terdefinisi], [2, salah, tidak terdefinisi, tidak terdefinisi],
[3, -378, tidak terdefinisi, tidak terdefinisi], ['a', '337', tidak terdefinisi, tidak terdefinisi]]

console.log(zipLongest(
    null,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, null], [2, false, null, null], [3, -378, null, null], ['a', '337', null, null]]

console.log(zipLongest(
    'Is None',
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, 'Is None'], [2, false, 'Is None', 'Is None'],
[3, -378, 'Is None', 'Is None'], ['a ',' 337 ',' Is None ',' Is None ']]


4

Anda dapat membuat fungsi utilitas dengan menggunakan ES6.

const zip = (arr, ...arrs) => {
  return arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]));
}

// example

const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];

console.log(zip(array1, array2));                  // [[1, 'a'], [2, 'b'], [3, 'c']]
console.log(zip(array1, array2, array3));          // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]

Namun, dalam panjang solusi di atas array pertama mendefinisikan panjang array output.

Inilah solusi di mana Anda memiliki kendali lebih besar terhadapnya. Agak rumit tapi sepadan.

function _zip(func, args) {
  const iterators = args.map(arr => arr[Symbol.iterator]());
  let iterateInstances = iterators.map((i) => i.next());
  ret = []
  while(iterateInstances[func](it => !it.done)) {
    ret.push(iterateInstances.map(it => it.value));
    iterateInstances = iterators.map((i) => i.next());
  }
  return ret;
}
const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];

const zipShort = (...args) => _zip('every', args);

const zipLong = (...args) => _zip('some', args);

console.log(zipShort(array1, array2, array3)) // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]
console.log(zipLong([1,2,3], [4,5,6, 7]))
// [
//  [ 1, 4 ],
//  [ 2, 5 ],
//  [ 3, 6 ],
//  [ undefined, 7 ]]


4

1. Modul NPM: zip-array

Saya menemukan modul npm yang dapat digunakan sebagai versi javascript python zip:

zip-array - Javascript yang setara dengan fungsi zip Python. Menggabungkan nilai-nilai dari masing-masing array.

https://www.npmjs.com/package/zip-array

2. tf.data.zip()di Tensorflow.js

Pilihan alternatif lain adalah untuk pengguna Tensorflow.js: jika Anda memerlukan zipfungsi dalam python untuk bekerja dengan dataset tensorflow dalam Javascript, Anda dapat menggunakan tf.data.zip()Tensorflow.js.

tf.data.zip () di Tensorflow.js didokumentasikan di sini


3

Bukan bawaan untuk Javascript itu sendiri. Beberapa kerangka kerja Javascript umum (seperti Prototipe) menyediakan implementasi, atau Anda dapat menulis sendiri.


1
Tautan? Juga, saya akan lebih tertarik jika jQuery melakukannya, karena itulah yang saya gunakan ...
pq.


2
Namun perlu dicatat bahwa jQuery satu berperilaku sedikit berbeda dari yang Python, dalam hal itu mengembalikan objek, bukan array ... dan dengan demikian tidak dapat zip lebih dari 2 daftar bersama.
Amber

Benar, penulis tidak seharusnya menyebut jQuery yang setara.
pq.

3

Seperti @Brandon, saya sarankan Menggarisbawahi 's zip fungsi. Namun, ini bertindak seperti zip_longest, menambahkan undefinednilai yang diperlukan untuk mengembalikan sesuatu yang panjang dari input terpanjang.

Saya menggunakan mixinmetode untuk memperluas garis bawah dengan zipShortest, yang bertindak seperti Python zip, berdasarkan dari sumber perpustakaan itu sendirizip .

Anda dapat menambahkan berikut ini ke kode JS umum Anda dan kemudian menyebutnya seolah-olah itu bagian dari garis bawah: _.zipShortest([1,2,3], ['a'])mengembalikan [[1, 'a']], misalnya.

// Underscore library addition - zip like python does, dominated by the shortest list
//  The default injects undefineds to match the length of the longest list.
_.mixin({
    zipShortest : function() {
        var args = Array.Prototype.slice.call(arguments);
        var length = _.min(_.pluck(args, 'length')); // changed max to min
        var results = new Array(length);
        for (var i = 0; i < length; i++) {
            results[i] = _.pluck(args, "" + i);
        }
        return results;
}});

Downvote tanpa komentar? Saya senang memperbaiki jawaban ini, tetapi tidak bisa tanpa umpan balik.
Pat

2

Anda bisa mengurangi larik array dan memetakan array baru dengan mengambil hasil indeks array dalam.

var array1 = [1, 2, 3],
    array2 = ['a','b','c'],
    array3 = [4, 5, 6],
    array = [array1, array2, array3],
    transposed = array.reduce((r, a) => a.map((v, i) => (r[i] || []).concat(v)), []);

console.log(transposed);


1

Variasi dari solusi generator malas :

function* iter(it) {
    yield* it;
}

function* zip(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.some(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zip([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

// the only change for "longest" is some -> every

function* zipLongest(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.every(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zipLongest([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

Dan ini adalah idiom klasik "n-group" python zip(*[iter(a)]*n):

triples = [...zip(...Array(3).fill(iter(a)))]

Saya bertanya-tanya apa yang salah dengan yang ini, saya menulis yang sama persis. Bagi saya rasanya jauh lebih baik daripada yang lain, tapi mungkin kita berdua salah ... Saya sedang mencari cara untuk menambahkan tipe aliran ke dalamnya, tapi saya berjuang: D.
cglacet

0

The MochiKit perpustakaan menyediakan ini dan banyak lainnya Python-seperti fungsi. pengembang Mochikit juga merupakan penggemar Python, sehingga memiliki gaya umum Python, dan juga membungkus panggilan async dalam kerangka kerja yang memutar.


0

Saya mencoba ini di JS murni bertanya-tanya bagaimana plugin yang diposting di atas menyelesaikan pekerjaan. Inilah hasil saya. Saya akan membuka ini dengan mengatakan bahwa saya tidak tahu seberapa stabil ini di IE dan sejenisnya. Itu hanya mockup cepat.

init();

function init() {
    var one = [0, 1, 2, 3];
    var two = [4, 5, 6, 7];
    var three = [8, 9, 10, 11, 12];
    var four = zip(one, two, one);
    //returns array
    //four = zip(one, two, three);
    //returns false since three.length !== two.length
    console.log(four);
}

function zip() {
    for (var i = 0; i < arguments.length; i++) {
        if (!arguments[i].length || !arguments.toString()) {
            return false;
        }
        if (i >= 1) {
            if (arguments[i].length !== arguments[i - 1].length) {
                return false;
            }
        }
    }
    var zipped = [];
    for (var j = 0; j < arguments[0].length; j++) {
        var toBeZipped = [];
        for (var k = 0; k < arguments.length; k++) {
            toBeZipped.push(arguments[k][j]);
        }
        zipped.push(toBeZipped);
    }
    return zipped;
}

Ini bukan antipeluru, tapi masih menarik.


jsfiddle terlihat bagus. Memiliki tombol TidyUp! Tombol Jalankan tidak menampilkan output console.log Anda di panel Hasil. Mengapa?
pq.

Itu (console.log) membutuhkan sesuatu seperti Firebug untuk dijalankan. Beralih saja console.logke alert.

Untuk apa panel Hasil?
pq.

Ini menunjukkan HTML biola. Dalam hal ini, saya hanya melakukan JS lurus. Inilah hasilnya menggunakan document.write() jsfiddle.net/PyTWw/5

-1

Ini memotong garis dari jawaban berbasis iterator Ddi :

function* zip(...toZip) {
  const iterators = toZip.map((arg) => arg[Symbol.iterator]());
  const next = () => toZip = iterators.map((iter) => iter.next());
  while (next().every((item) => !item.done)) {
    yield toZip.map((item) => item.value);
  }
}

-1

Jika Anda baik-baik saja dengan ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.