Equivalente Javascript della funzione zip di Python


216

Esiste un equivalente javascript della funzione zip di Python? Cioè, dati più array di uguale lunghezza creano una matrice di coppie.

Ad esempio, se ho tre array che assomigliano a questo:

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

L'array di output dovrebbe essere:

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

5
È giusto dire che noi programmatori di Python abbiamo "paura" dei metodi stupidi che coinvolgono i loop perché sono lenti e quindi cercano sempre metodi integrati per fare le cose. Ma che in Javascript dovremmo semplicemente andare avanti e scrivere i nostri loop perché non sono particolarmente lenti?
Londra,

3
@LondonRob Un loop è un loop, nascosto dietro un metodo 'veloce' o no. JavaScript è stato sicuramente sempre più il supporto per funzioni di ordine superiore, con l'introduzione di Array di forEach, reduce, map, every, ecc E 'stato proprio il caso che zipnon ha "il taglio" (una flatMapè anche assente), non per considerazioni sulle prestazioni - ma per essere onesti, .NET (3.5) non ha avuto un Zip su Enumerable per un paio d'anni! Qualsiasi libreria "funzionale" come underscore / lodash (lodash 3.x ha una valutazione della sequenza pigra) fornirà una funzione zip equivalente.
user2864740

@ user2864740 Un loop interpretato (come in Python) sarà sempre molto più lento di un loop di codice macchina. Un loop compilato da JIT (come nei moderni motori JS) può avvicinarsi alla velocità della CPU nativa, al punto che il guadagno introdotto utilizzando un loop di codice macchina può essere compensato dall'overhead della chiamata di funzione anonima. Tuttavia, ha senso avere queste funzioni integrate e profilare diverse varianti dei "loop interni" con diversi motori JS. I risultati potrebbero non essere ovvi.
Tobia,

Risposte:


178

Aggiornamento 2016:

Ecco una versione di Snazzier Ecmascript 6:

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

Illustrazione equiv. a Python { zip(*args)}:

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

(e FizzyTea sottolinea che ES6 ha una sintassi dell'argomento variadico, quindi la seguente definizione di funzione agirà come python, ma vedi sotto per disclaimer ... questo non sarà il suo zip(zip(x))contrario quindi non sarà uguale x; sebbene come sottolinea Matt Kramer zip(...zip(...x))==x(come in pitone normale zip(*zip(*x))==x))

Definizione alternativa equiv. a 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

(Si noti che la ...sintassi potrebbe avere problemi di prestazioni in questo momento, e forse in futuro, quindi se si utilizza la seconda risposta con argomenti variadici, è possibile che si desideri perfezionarla.)


Ecco un 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(...)

Quanto sopra presuppone che le matrici abbiano le stesse dimensioni, come dovrebbero essere. Presuppone anche che passi in un singolo elenco di argomenti list, a differenza della versione di Python in cui l'elenco degli argomenti è variabile. Se vuoi tutte queste "caratteristiche", vedi sotto. Sono necessarie solo circa 2 righe di codice aggiuntive.

Quanto segue imiterà il zipcomportamento di Python in casi limite in cui le matrici non hanno le stesse dimensioni, fingendo silenziosamente che non esistano parti più lunghe delle matrici:

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()
// []

Questo imiterà il itertools.zip_longestcomportamento di Python , inserendo undefineddove non sono definite le matrici:

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()
// []

Se usi queste ultime due versioni (variadic aka. Versioni con argomenti multipli), zip non è più il suo contrario. Per imitare il zip(*[...])linguaggio di Python, dovrai farlo zip.apply(this, [...])quando vuoi invertire la funzione zip o se vuoi avere un numero variabile di liste come input.


addendum :

Per rendere questo handle qualsiasi iterabile (ad esempio in Python è possibile utilizzare zipstringhe, intervalli, oggetti mappa, ecc.), È possibile definire quanto segue:

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

Tuttavia, se scrivi zipnel modo seguente , anche quello non sarà necessario:

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]]

(Oppure potresti usare una range(...)funzione in stile Python se ne hai già scritta una. Alla fine sarai in grado di usare la comprensione o i generatori di array ECMAScript.)


1
Questo non funziona per me: TypeError: Object 1 non ha un metodo 'map'
Emanuele Paolini

7
E ES6 per args variadici ed eventuali iterabili:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983,

"L'oggetto 1 non ha un metodo 'map'" probabilmente è un caso di tentativo di utilizzare questo su un oggetto che non ha un metodo map (come un nodelist o una stringa) che è stato coperto nell'addendum di questo post
ninjagecko,

Mentre è vero che la versione variadic ES6 non conserva zip(zip(x)) = x, puoi comunque crogiolarti nella fiducia che zip(...zip(...x)) = x.
Matt Kramer,

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

34

Dai un'occhiata alla libreria Underscore .

Underscore offre oltre 100 funzioni che supportano sia i tuoi aiutanti funzionali preferiti del giorno lavorativo: mappa, filtro, invocazione, sia altri gadget specializzati: associazione di funzioni, template javascript, creazione di indici rapidi, test di uguaglianza profonda e così via.

- Di 'le persone che ce l'hanno fatta

Di recente ho iniziato a usarlo appositamente per la zip()funzione e ha lasciato un'ottima prima impressione. Sto usando jQuery e CoffeeScript, e si adatta perfettamente a loro. Underscore riprende proprio da dove si interrompono e finora non mi ha deluso. Oh, a proposito, è solo 3kb minimizzato.

Controlla:

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

3
Quando usi Underscore, ti senti un po 'più vicino alla chiarezza e all'intimità logica di Haskell.
CamilB,

12
Invece di sottolineare, prova questo: lodash.com - sostituzione drop-in, stesso sapore eccezionale, più funzionalità, maggiore coerenza tra browser, migliore perf. Vedi kitcambridge.be/blog/say-hello-to-lo-dash per una descrizione.
Merlyn Morgan-Graham,

16

Oltre alla risposta eccellente e completa di ninjagecko, tutto ciò che serve per comprimere due array JS in un "imitazione tupla" è:

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

Spiegazione:
Poiché Javascript non ha un tuplestipo, le funzioni per tuple, elenchi e set non costituivano una priorità assoluta nelle specifiche della lingua.
Altrimenti, un comportamento simile è accessibile in modo semplice tramite la mappa di array in JS> 1.6 . (in maprealtà è spesso implementato dai produttori di motori JS in molti motori> JS 1.4, nonostante non sia specificato).
La principale differenza a Python di zip, izip, ... i risultati di mapstile funzionale 's, in quanto maprichiede una funzione-argomento. Inoltre è una funzione Arraydell'istanza. Si può usare Array.prototype.mapinvece, se una dichiarazione aggiuntiva per l'input è un problema.

Esempio:

_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]]})

Risultato:

//'('+_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)

Prestazioni correlate:

Utilizzando mapover- forloop:

Vedi: Qual è il modo più efficiente di unire [1,2] e [7,8] in [[1,7], [2,8]]

test zip

Nota: i tipi di base come falsee undefinednon presentano una gerarchia di oggetti prototipo e quindi non espongono una toStringfunzione. Quindi questi sono mostrati come vuoti nell'output.
Come parseIntsecondo argomento è la radice base / numero, in cui convertire il numero, e poiché mappassa l'indice come secondo argomento alla sua funzione argomento, viene utilizzata una funzione wrapper.


Il tuo primo esempio dice "aIn non è una funzione" quando lo provo. Funziona se chiamo .map dall'array anziché come prototipo: aIn.map(function(e, i) {return [e, aOut[i]];})cosa c'è che non va?
Noumenon,

1
@Noumenon, Array.prototype.mapavrebbe dovuto essere Array.prototype.map.call, risolto la risposta.
utente

11

Esempio ES6 moderno con un generatore:

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 )
    }
}

Innanzitutto, otteniamo un elenco di iterables come iterators. Questo di solito accade in modo trasparente, ma qui lo facciamo esplicitamente, mentre cediamo passo dopo passo fino a quando uno di essi è esaurito. Controlliamo se uno qualsiasi dei risultati (usando il .some()metodo) nell'array dato è esaurito e, in tal caso, interrompiamo il ciclo while.


Questa risposta potrebbe usare più spiegazioni.
Cmaher

1
Otteniamo un elenco di iteratori da iterables. Questo di solito accade in modo trasparente, qui lo facciamo esplicitamente, mentre cediamo passo dopo passo fino a quando uno di loro è esaurito. Controlla se qualcuno di essi (il metodo .some ()) nell'array è esaurito e noi interrompiamo se è così.
Dimitris,

11

Insieme ad altre funzioni simili a Python, pythonicoffre una zipfunzione, con l'ulteriore vantaggio di restituire una valutazione pigra Iterator, simile al comportamento della sua controparte Python :

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))];

Divulgazione Sono autore e manutentore di Pythonic


7

Python ha due funzioni: zip e itertools.zip_longest. L'implementazione su JS / ES6 è così:

Implementazione zip di Python su 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])));
};

risultati:

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],
    []
));

[]

Implementazione Python`s zip_longest su 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
        )
    );
};

risultati:

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

[[1, 667, 111, non definito], [2, falso, indefinito, indefinito],
[3, -378, indefinito, indefinito], ['a', '337', indefinito, indefinito]]

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

È possibile eseguire la funzione di utilità utilizzando 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]]

Tuttavia, nella soluzione sopra la lunghezza del primo array definisce la lunghezza dell'array di output.

Ecco la soluzione in cui hai più controllo su di essa. È un po 'complesso ma ne vale la pena.

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. Modulo Npm: zip-array

Ho trovato un modulo npm che può essere usato come versione javascript di python zip:

zip-array - Un equivalente javascript della funzione zip di Python. Unisce i valori di ciascuno degli array.

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

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

Un'altra scelta alternativa è per gli utenti di Tensorflow.js: se hai bisogno di una zipfunzione in Python per lavorare con i set di dati di Tensorflow in Javascript, puoi usare tf.data.zip()in Tensorflow.js.

tf.data.zip () in Tensorflow.js documentato qui


3

Non incorporato in Javascript stesso. Alcuni dei framework JavaScript comuni (come Prototype) forniscono un'implementazione, oppure puoi scrivere il tuo.


1
Link? Inoltre, sarei più interessato se lo facesse jQuery, dato che è quello che sto usando ...
pq.


2
Si noti tuttavia che jQuery si comporta in modo leggermente diverso da quello di Python, in quanto restituisce un oggetto, non un array ... e quindi non può comprimere più di 2 elenchi insieme.
Ambra

Bene, l'autore non dovrebbe chiamare jQuery uno equivalente.
pq.

3

Come @Brandon, raccomando la funzione zip di Underscore . Tuttavia, si comporta come , aggiungendo i valori necessari per restituire qualcosa della lunghezza dell'input più lungo.zip_longestundefined

Ho usato il mixinmetodo per estendere il carattere di sottolineatura con a zipShortest, che si comporta come quello di Python zip, basato sul sorgente della libreria perzip .

È possibile aggiungere quanto segue al codice JS comune e quindi chiamarlo come se fosse parte del carattere di sottolineatura: _.zipShortest([1,2,3], ['a'])restituisce [[1, 'a']], ad esempio.

// 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 senza un commento? Sono felice di migliorare questa risposta, ma non posso senza feedback.
Pat

2

È possibile ridurre l'array di array e mappare il nuovo array prendendo il risultato dell'indice dell'array interno.

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

Una variante della soluzione del generatore pigro :

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())

E questo è il classico linguaggio "n-group" di Python zip(*[iter(a)]*n):

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

Mi chiedo cosa c'è di sbagliato in questo, ho scritto esattamente lo stesso. Per me è molto meglio di tutti gli altri, ma forse ci sbagliamo entrambi ... Stavo cercando un modo per aggiungere tipi di flusso, ma sto lottando: D.
Cglacet,

0

La libreria Mochikit fornisce questa e molte altre funzioni simili a Python. lo sviluppatore di Mochikit è anche un fan di Python, quindi ha lo stile generale di Python e avvolge anche le chiamate asincrone in un framework contorto.


0

Ho provato a farlo in puro JS chiedendomi come i plugin pubblicati sopra facessero il loro lavoro. Ecco il mio risultato Premetto questo dicendo che non ho idea di quanto questo sarà stabile in IE e simili. È solo un breve modello.

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;
}

Non è a prova di proiettile, ma è comunque interessante.


jsfiddle sembra carino. Ha un pulsante TidyUp! Il pulsante Esegui non ha mostrato l'output console.log nel pannello Risultato. Perché?
pq.

(Console.log) richiede qualcosa come Firebug per funzionare. Passa console.loga alert.

A cosa serve il riquadro dei risultati?
pq.

Mostra l'HTML del violino. In questo caso, sto solo facendo JS dritto. Ecco il risultato usando document.write() jsfiddle.net/PyTWw/5

-1

Questo elimina una linea dalla risposta basata su iteratore di 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

Se stai bene con ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.