web-dev-qa-db-fra.com

Équivalent Javascript de la fonction Zip de Python

Existe-t-il un équivalent javascript de la fonction Zip de Python? Autrement dit, si plusieurs tableaux de longueurs égales sont créés, créez 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 en sortie devrait être:

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

Mise à jour 2016:

Voici une version plus élégante d'Ecmascript 6:

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 fait remarquer que ES6 a une syntaxe d'argument variable, donc la définition de fonction suivante agira comme python, mais voyez ci-dessous l'avertissement ... ce ne sera pas son propre inverse, donc Zip(zip(x)) ne sera pas égal à x; bien que, comme le souligne Matt Kramer Zip(...Zip(...x))==x (comme dans un python régulier Zip(*Zip(*x))==x))

Autre définition é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 poser des problèmes de performances à ce stade, voire dans le futur. Si vous utilisez la deuxième réponse avec des arguments variadiques, vous souhaiterez 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 ont la même taille, comme il se doit. Cela suppose également que vous passiez dans un seul argument de liste de listes, contrairement à la version de Python dans laquelle 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.

Ce qui suit imitera le comportement Zip de Python sur les cas Edge où les tableaux ne sont pas de taille identique, prétendant en silence que les parties les plus longues 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 comportement itertools.Zip_longest de Python, en insérant undefined où 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, également appelée versions à plusieurs arguments), alors Zip n’est plus son propre inverse. Pour imiter l'idiome Zip(*[...]) de Python, vous devez utiliser Zip.apply(this, [...]) lorsque vous souhaitez inverser la fonction Zip ou si vous souhaitez également avoir un nombre variable de listes en entrée.


Addenda:

Pour rendre ce descripteur quelconque (par exemple, en Python, vous pouvez utiliser Zip sur 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 Zip dans le chemin suivant, 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]]

(Vous pouvez également utiliser une fonction de style range(...) de type Python si vous en avez déjà écrit une. Vous pourrez éventuellement utiliser des interprétations ou des générateurs de tableaux ECMAScript.)

148
ninjagecko

Découvrez la bibliothèque Underscore .

Underscore fournit plus de 100 fonctions qui prennent en charge à la fois vos aides fonctionnelles préférées: map, filter, invoke - ainsi que des goodies plus spécialisés: liaison de fonction, création de modèles javascript, création d'index rapides, tests d'égalité approfondie, etc.

- Dites les gens qui l'ont fait

J'ai récemment commencé à l'utiliser spécifiquement pour la fonction Zip() et cela a laissé une excellente première impression. J'utilise jQuery et CoffeeScript, et cela leur convient parfaitement. Souligner soulève juste là où ils se sont arrêtés et jusqu'à présent, ça ne m'a pas laissé tomber. Oh, au fait, c'est seulement 3kb minified.

Vérifiez-le.

28
Brandon

En plus de l'excellente et complète réponse de ninjagecko, il suffit de compresser deux tableaux JS en un "simulacre de tuple":

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

Explication:
Comme Javascript n’a pas de type tuples, les fonctions pour les n-uplets, les listes et les ensembles n’étaient pas une priorité élevée dans la spécification du langage.
Sinon, un comportement similaire est facilement accessible via Array map in JS> 1.6 . (map est en fait souvent implémenté par les constructeurs de moteurs JS dans de nombreux moteurs> JS 1.4, bien que non spécifié).
La différence majeure par rapport au style Zip, izip, ... de Python provient du style fonctionnel de map, car map nécessite un argument fonction. De plus, il est une fonction de l'instance Array-. On peut utiliser Array.prototype.map à la place, 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 map sur for- boucles:

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

Zip tests

Remarque: les types de base tels que false et undefined ne possèdent pas de hiérarchie d'objet prototypique et n'exposent donc pas une fonction toString. Par conséquent, ceux-ci sont affichés comme vides dans la sortie.
Comme le deuxième argument de parseInt est la base/nombre radix, vers lequel convertir le nombre, et puisque map passe l'index comme deuxième argument de sa fonction argument, une fonction wrapper est utilisée.

12
Lorenz Lo Sauer

Le Python a deux fonctions: Zip et itertools.Zip_longest. L'implémentation sur JS/ES6 est la suivante:

Implémentation 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, false, indéfini, indéfini],
[3, -378, non défini, non défini], ['a', '337', non dé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, «n'est rien»], [2, faux, «n'est rien», «n'est rien»],
[3, -378, 'Is None', 'Is None'], ['a', '337', 'Is None', 'Is Aucun' ] ]

4
Seti Volkylany

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 sous la forme iterators. Cela se produit généralement de manière transparente, mais nous le faisons ici explicitement, en cédant étape par étape jusqu'à ce que l'un d'entre eux soit épuisé. Nous vérifions si l'un des résultats (à l'aide de la méthode .some()) du tableau donné est épuisé et, le cas échéant, nous cassons la boucle while.

4
Dimitris

Vous pouvez rendre la fonction 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 de la première matrice définit la longueur de la matrice 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 ]]
3
Bhargav Patel

Non intégré à Javascript lui-même. Certains frameworks Javascript courants (tels que Prototype) fournissent une implémentation ou vous pouvez écrire votre propre.

3
Amber

Pythonic offre Zip avec d'autres fonctions semblables à python:

import {Zip} 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
3
Keyvan

1. Module Npm: Zip-array

J'ai trouvé un module npm pouvant être utilisé comme version javascript de python Zip:

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

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

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

Un autre choix est pour les utilisateurs de Tensorflow.js: si vous avez besoin d’une fonction Zip en python pour utiliser les jeux de données tensorflow en Javascript, vous pouvez utiliser tf.data.Zip() dans Tensorflow.js.

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

2
Huan

Comme @Brandon, je recommande Underscore 's Zip function. Cependant, il agit comme Zip_longest, en ajoutant les valeurs undefined selon les besoins pour renvoyer quelque chose de la longueur de la plus longue entrée. 

J'ai utilisé la méthode mixin pour étendre le trait de soulignement avec un zipShortest, qui agit comme le Zip de Python, basé sur la source de la bibliothèque pour Zip

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']) renvoie [[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;
}});
2
Pat

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

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
Nina Scholz

La bibliothèque Mochikit fournit cette fonction et de nombreuses autres fonctions similaires à Python. Le développeur de Mochikit est également un fan de Python, il a donc le style général de Python et englobe les appels asynchrones dans un framework tordu.

0
Keith

Une variante de la solution du générateur lazy :

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 Zip(*[iter(a)]*n) du python:

triples = [...Zip(...Array(3).fill(iter(a)))]
0
georg

Je me suis lancé à fond dans cette pure JS en me demandant comment les plugins indiqués ci-dessus permettaient de faire le travail. Voici mon résultat. Je commencerai par dire que je ne sais pas du tout à quel point cela sera stable dans IE, etc. C'est juste une rapide maquette.

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 pare-balles, mais c'est quand même intéressant.

0
user1385191