Equivalent Javascript de la fonction zip de Python


216

Existe-t-il un équivalent javascript de la fonction zip de Python? Autrement dit, étant donné que plusieurs tableaux de longueurs égales créent un tableau de paires.

Par exemple, si j'ai trois tableaux qui ressemblent à ceci:

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

Le tableau de sortie doit être:

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

5
Est-il juste de dire que nous, les programmeurs Python, avons «peur» des méthodes stupides impliquant des boucles parce qu'elles sont lentes, et donc recherchons toujours des méthodes intégrées pour faire les choses. Mais qu'en Javascript, nous devrions simplement continuer et écrire nos boucles car elles ne sont pas particulièrement lentes?
LondonRob

3
@LondonRob Une boucle est une boucle, cachée ou non derrière une méthode «rapide». JavaScript a certainement été faire plus de soutien pour les fonctions d'ordre supérieur, avec l'introduction de ce tableau forEach, reduce, map, every, etc. C'était le cas qui zipn'a pas « faire la coupe » (un flatMapest également absent), et non pour des considérations de performance - mais pour être juste, .NET (3.5) n'a pas eu de Zip sur Enumerable pendant quelques années! Toute bibliothèque «fonctionnelle» comme le soulignement / lodash (lodash 3.x a une évaluation de séquence paresseuse) fournira une fonction zip équivalente.
user2864740

@ user2864740 Une boucle interprétée (comme en Python) sera toujours beaucoup plus lente qu'une boucle de code machine. Une boucle compilée JIT (comme dans les moteurs JS modernes) peut approcher la vitesse native du processeur, à tel point que le gain introduit en utilisant une boucle de code machine peut être compensé par la surcharge de l'appel de fonction anonyme. Pourtant, il est logique d'avoir ces fonctions intégrées et de profiler plusieurs variantes de vos "boucles internes" avec plusieurs moteurs JS. Les résultats peuvent ne pas être évidents.
Tobia

Réponses:


178

Mise à jour 2016:

Voici une version Ecmascript 6 plus brillante:

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

Illustration équiv. vers Python { zip(*args)}:

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

(et FizzyTea souligne que ES6 a une syntaxe d'argument variadique, donc la définition de fonction suivante agira comme python, mais voir ci-dessous pour l'avertissement ... ce ne sera pas son propre inverse donc zip(zip(x))ne sera pas égal x; bien que Matt Kramer le souligne zip(...zip(...x))==x(comme en python normal zip(*zip(*x))==x))

Définition alternative équiv. vers 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

(Notez que la ...syntaxe peut avoir des problèmes de performances à ce stade, et peut-être à l'avenir, donc si vous utilisez la deuxième réponse avec des arguments variadiques, vous voudrez peut-être la tester.)


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

Ce qui précède suppose que les tableaux sont de taille égale, comme ils devraient l'être. Il suppose également que vous transmettez un seul argument de liste de listes, contrairement à la version de Python où la liste d'arguments est variadique. Si vous voulez toutes ces "fonctionnalités", voir ci-dessous. Il faut environ 2 lignes de code supplémentaires.

Les éléments suivants imiteront le zipcomportement de Python dans les cas de périphérie où les tableaux ne sont pas de taille égale, en prétendant silencieusement que les parties les plus longues des tableaux n'existent pas:

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

Cela imitera le itertools.zip_longestcomportement de Python , en insérant undefinedoù les tableaux ne sont pas définis:

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

Si vous utilisez ces deux dernières versions (variadic aka. Versions à arguments multiples), alors zip n'est plus son propre inverse. Pour imiter l' zip(*[...])idiome de Python, vous devrez le faire zip.apply(this, [...])lorsque vous souhaitez inverser la fonction zip ou si vous souhaitez également avoir un nombre variable de listes en entrée.


addendum :

Pour que cette poignée soit itérable (par exemple en Python que vous pouvez utiliser zipsur des chaînes, des plages, des objets de carte, etc.), vous pouvez définir les éléments suivants:

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

Cependant, si vous écrivez zipde la manière suivante , même cela ne sera pas nécessaire:

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

Démo:

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

(Ou vous pouvez utiliser une range(...)fonction de style Python si vous en avez déjà écrite une. Finalement, vous pourrez utiliser des générateurs ou des compréhensions de tableaux ECMAScript.)


1
Cela ne fonctionne pas pour moi: TypeError: l'objet 1 n'a pas de méthode 'map'
Emanuele Paolini

7
Et ES6 pour les arguments variadiques et tout itérable:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983

l '"objet 1 n'a pas de méthode' map '" est probablement un cas d'essayer de l'utiliser sur un objet qui n'a pas de méthode map (comme une nodelist ou une chaîne) qui a été couvert dans l'addendum de ce post
ninjagecko

S'il est vrai que la version variadic ES6 ne se conserve pas zip(zip(x)) = x, vous pouvez toujours vous en douter zip(...zip(...x)) = x.
Matt Kramer

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

34

Découvrez la bibliothèque Underscore .

Underscore fournit plus de 100 fonctions qui prennent en charge à la fois vos assistants fonctionnels préférés: mapper, filtrer, invoquer - ainsi que des avantages plus spécialisés: liaison de fonctions, création de modèles javascript, création d'index rapides, tests d'égalité approfondis, etc.

- Dis aux gens qui l'ont fait

J'ai récemment commencé à l'utiliser spécifiquement pour la zip()fonction et cela a laissé une excellente première impression. J'utilise jQuery et CoffeeScript, et cela va parfaitement avec eux. Underscore reprend là où ils se sont arrêtés et jusqu'à présent, il ne m'a pas déçu. Oh au fait, ce n'est que 3 Ko minifiés.

Vérifiez-le:

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

3
Lorsque vous utilisez Underscore, vous vous sentez un peu plus proche de la clarté et du confort logique de Haskell.
CamilB

12
Au lieu de souligner, essayez ceci: lodash.com - remplacement sans rendez -vous, même grande saveur, plus de fonctionnalités, plus de cohérence entre les navigateurs, meilleure performance. Voir kitcambridge.be/blog/say-hello-to-lo-dash pour une description.
Merlyn Morgan-Graham

16

En plus de l'excellente et complète réponse de ninjagecko, tout ce qu'il faut pour compresser deux tableaux JS en un "tuple-mimic" est:

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

Explication:
Puisque Javascript n'a pas detuples type, les fonctions pour les tuples, les listes et les ensembles n'étaient pas une priorité élevée dans la spécification du langage.
Sinon, un comportement similaire est accessible de manière simple via Array map dans JS> 1.6 . ( mapest en fait souvent implémenté par les fabricants de moteurs JS dans de nombreux moteurs> JS 1.4, bien qu'il ne soit pas spécifié).
La principale différence de Python zip, izip... les résultats de mapstyle fonctionnel de », étant donné que mapnécessite une fonction argument. De plus, c'est une fonction de l' Arrayinstance. On peut utiliser à la Array.prototype.mapplace, si une déclaration supplémentaire pour l'entrée est un problème.

Exemple:

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

Résultat:

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

Performances associées:

Utilisation de mapsur- forboucles:

Voir: Quel est le moyen le plus efficace de fusionner [1,2] et [7,8] en [[1,7], [2,8]]

tests zip

Remarque: les types de base tels que falseet undefinedne possèdent pas de hiérarchie d'objet prototypique et n'exposent donc pas de toStringfonction. Par conséquent, ceux-ci sont affichés comme vides dans la sortie. Le deuxième argument de
As parseIntest la base / nombre radix, en laquelle convertir le nombre, et puisquemap passe l'index comme deuxième argument à sa fonction argument, une fonction wrapper est utilisée.


Votre premier exemple dit "aIn n'est pas une fonction" lorsque je l'essaye. Cela fonctionne si j'appelle .map à partir du tableau plutôt que comme prototype: aIn.map(function(e, i) {return [e, aOut[i]];})qu'est-ce qui ne va pas?
Noumenon

1
@Noumenon, Array.prototype.mapaurait dû l'être Array.prototype.map.call, a corrigé la réponse.
utilisateur

11

Exemple ES6 moderne avec un générateur:

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

Tout d'abord, nous obtenons une liste d'itérables en tant que iterators. Cela se produit généralement de manière transparente, mais ici, nous le faisons explicitement, car nous cédons étape par étape jusqu'à ce que l'un d'eux soit épuisé. Nous vérifions si l'un des résultats (en utilisant la .some()méthode) dans le tableau donné est épuisé, et si c'est le cas, nous rompons la boucle while.


Cette réponse pourrait utiliser plus d'explications.
cmaher

1
Nous obtenons une liste d'itérateurs d'itérables. Cela se produit généralement de manière transparente, ici nous le faisons explicitement, car nous cédons étape par étape jusqu'à ce que l'un d'eux soit épuisé. Vérifiez si l'un d'eux (la méthode .some ()) dans le tableau est épuisé et nous l'interrompons si c'est le cas.
Dimitris

11

Parallèlement à d'autres fonctions de type Python, pythonicoffre une zipfonction, avec l'avantage supplémentaire de renvoyer un paresseux évalué Iterator, similaire au comportement de son homologue 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))];

Divulgation Je suis auteur et mainteneur de Pythonic


7

Le Python a deux fonctions: zip et itertools.zip_longest. L'implémentation sur JS / ES6 est comme ceci:

Implémentation du zip de Python sur 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])));
};

Résultats:

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, faux, 212], [3, -378, 323], ['a', '337', 433]]

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

[]

Implémentation de zip_longest de Python sur 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
        )
    );
};

Résultats:

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

[[1, 667, 111, indéfini], [2, faux, indéfini, indéfini],
[3, -378, indéfini, indéfini], ['a', '337', indéfini, indéfini]]

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'], [' ',' 337 ',' Is None ',' Is None ']]


4

Vous pouvez faire fonctionner l'utilitaire en utilisant 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]]

Cependant, dans la solution ci-dessus, la longueur du premier tableau définit la longueur du tableau de sortie.

Voici la solution dans laquelle vous avez plus de contrôle sur elle. C'est un peu complexe mais ça vaut le coup.

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

J'ai trouvé un module npm qui peut être utilisé comme une version javascript de python zip:

zip-array - Un équivalent javascript de la fonction zip de Python. Fusionne ensemble les valeurs de chacun des tableaux.

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

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

Un autre choix alternatif est pour les utilisateurs de Tensorflow.js: si vous avez besoin d'une zipfonction en python pour travailler avec des jeux de données tensorflow en Javascript, vous pouvez utiliser tf.data.zip()dans Tensorflow.js.

tf.data.zip () dans Tensorflow.js documenté à ici


3

Pas intégré à Javascript lui-même. Certains des cadres Javascript courants (tels que Prototype) fournissent une implémentation, ou vous pouvez écrire le vôtre.


1
Lien? De plus, je serais plus intéressé si jQuery le faisait, puisque c'est ce que j'utilise ...
pq.


2
Notez cependant que celui de jQuery se comporte légèrement différemment de celui de Python, en ce sens qu'il renvoie un objet, pas un tableau ... et ne peut donc pas compresser plus de 2 listes ensemble.
Amber

À droite, l'auteur ne devrait pas appeler l'équivalent jQuery.
pq.

3

Comme @Brandon, je recommande la fonction zip d' Underscore . Cependant, il agit comme suit:zip_longestundefined valeurs au besoin pour renvoyer quelque chose de la longueur de l'entrée la plus longue.

J'ai utilisé la mixinméthode pour étendre le trait de soulignement avec un zipShortest, qui agit comme Python zip, basé sur la propre sourcezip de la bibliothèque pour .

Vous pouvez ajouter ce qui suit à votre code JS commun, puis l'appeler comme s'il faisait partie du trait de soulignement: _.zipShortest([1,2,3], ['a'])retours [[1, 'a']], par exemple.

// 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 sans commentaire? Je suis heureux d'améliorer cette réponse, mais je ne peux pas sans rétroaction.
Pat

2

Vous pouvez réduire le tableau de tableaux et mapper un nouveau tableau en prenant le résultat de l'index du tableau interne.

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

Une variante de la solution de générateur paresseux :

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

Et voici l'idiome classique du "n-groupe" du python zip(*[iter(a)]*n):

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

Je me demande ce qui ne va pas avec celui-ci, j'ai écrit exactement le même. Pour moi, cela semble beaucoup mieux que tous les autres, mais peut-être que nous nous trompons tous les deux ... Je cherchais un moyen d'y ajouter des types de flux, mais j'ai du mal: D.
cglacet

0

La bibliothèque Mochikit fournit cela et bien d'autres fonctions de type Python. Le développeur de Mochikit est également un fan de Python, il a donc le style général de Python, ainsi que l'encapsulation des appels asynchrones dans un cadre de type tordu.


0

Je me suis lancé dans JS pur en me demandant comment les plugins postés ci-dessus ont fait le travail. Voici mon résultat. Je préfère ceci en disant que je n'ai aucune idée de la stabilité de ce sera dans IE et autres. C'est juste une maquette rapide.

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

Ce n'est pas à l'épreuve des balles, mais c'est quand même intéressant.


jsfiddle a l'air sympa. Possède un bouton TidyUp! Le bouton Exécuter n'affiche pas votre sortie console.log dans le panneau Résultat. Pourquoi?
pq.

Il (console.log) nécessite quelque chose comme Firebug pour fonctionner. Passez simplement console.logà alert.

À quoi sert le volet Résultat?
pq.

Il montre le code HTML du violon. Dans ce cas, je fais juste du JS droit. Voici le résultat en utilisant document.write() jsfiddle.net/PyTWw/5

-1

Cela rase une ligne de la réponse basée sur itérateur de 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

Si vous êtes bien avec ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.