Javascript equivalente a la función zip de Python

216

¿Existe un javascript equivalente a la función zip de Python? Es decir, dadas múltiples matrices de igual longitud crean una matriz de pares.

Por ejemplo, si tengo tres matrices que se ven así:

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

La matriz de salida debe ser:

var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]
pq.
fuente
55
¿Es justo decir que los programadores de Python tenemos 'miedo' a los métodos tontos que involucran bucles porque son lentos y, por lo tanto, siempre buscan métodos integrados para hacer las cosas? ¿Pero que en Javascript deberíamos seguir adelante y escribir nuestros bucles porque no son particularmente lentos?
LondonRob
3
@LondonRob Un bucle es un bucle, oculto detrás de un método 'rápido' o no. JavaScript definitivamente ha sido cada vez más apoyo para las funciones de orden superior, con la introducción de Array forEach, reduce, map, every, etc. Fue simplemente el caso de que zipno "hacer el corte" (una flatMaptambién está ausente), no por consideraciones de rendimiento - pero para ser justos, .NET (3.5) no tuvo un Zip en Enumerable durante un par de años. Cualquier biblioteca 'funcionalista' como el subrayado / lodash (lodash 3.x tiene evaluación de secuencia diferida) proporcionará una función zip equivalente.
usuario2864740
@ user2864740 Un bucle interpretado (como en Python) siempre será mucho más lento que un bucle de código de máquina. Un bucle compilado JIT (como en los motores JS modernos) puede acercarse a la velocidad de la CPU nativa, tanto que la ganancia introducida mediante el uso de un bucle de código de máquina puede compensarse con la sobrecarga de la llamada de función anónima. Aún así, tiene sentido tener estas funciones integradas y perfilar varias variaciones de sus "bucles internos" con varios motores JS. Los resultados pueden no ser obvios.
Tobia

Respuestas:

178

Actualización 2016:

Aquí hay una versión más elegante de Ecmascript 6:

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

Ilustración equiv. a Python { zip(*args)}:

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

(y FizzyTea señala que ES6 tiene una sintaxis de argumento variable, por lo que la siguiente definición de función actuará como python, pero vea a continuación el descargo de responsabilidad ... esto no será su propio inverso, por zip(zip(x))lo que no será igual x; aunque como señala Matt Kramer zip(...zip(...x))==x(como en pitón normal zip(*zip(*x))==x))

Definición 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

(Tenga en cuenta que la ...sintaxis puede tener problemas de rendimiento en este momento, y posiblemente en el futuro, por lo que si usa la segunda respuesta con argumentos variables, es posible que desee probarla).


Aquí hay una línea:

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

Lo anterior supone que las matrices son del mismo tamaño, como deberían ser. También supone que pasa una sola lista de argumentos de listas, a diferencia de la versión de Python donde la lista de argumentos es variada. Si desea todas estas "características", consulte a continuación. Solo toma alrededor de 2 líneas adicionales de código.

Lo siguiente imitará el zipcomportamiento de Python en casos extremos donde las matrices no son del mismo tamaño, simulando en silencio que las partes más largas de las matrices no existen:

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

Esto imitará el itertools.zip_longestcomportamiento de Python , insertando undefineddonde las matrices no están definidas:

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 utiliza estas dos últimas versiones (también conocidas como versiones variadas de argumentos múltiples), entonces zip ya no es su propio inverso. Para imitar el zip(*[...])idioma de Python, deberá hacerlo zip.apply(this, [...])cuando desee invertir la función zip o si desea tener un número variable de listas como entrada.


Anexo :

Para hacer que este maneje sea iterable (por ejemplo, en Python puede usar zipcadenas, rangos, objetos de mapa, etc.), puede definir lo siguiente:

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

Sin embargo, si escribe zipde la siguiente manera , incluso eso no será necesario:

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

Manifestación:

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

(O podría usar una range(...)función de estilo Python si ya ha escrito una. Eventualmente podrá usar comprensiones o generadores de matriz ECMAScript).

ninjagecko
fuente
1
Esto no funciona para mí: TypeError: Object 1 no tiene método 'map'
Emanuele Paolini
77
Y ES6 para args variadic y cualquier iterable:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983
el "Objeto 1 no tiene método 'mapa'" probablemente sea un caso de tratar de usar esto en un objeto que no tiene un método de mapa (como una lista de nodos o una cadena) que se cubrió en el anexo de esta publicación
ninjagecko
Si bien es cierto que la versión variada ES6 no se conserva zip(zip(x)) = x, aún puede disfrutar de la confianza de que zip(...zip(...x)) = x.
Matt Kramer el
const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
Константин Ван
34

Echa un vistazo a la biblioteca subrayado .

Underscore proporciona más de 100 funciones que son compatibles con sus ayudantes funcionales favoritos del día de trabajo: mapear, filtrar, invocar, así como otras características más especializadas: enlace de funciones, creación de plantillas de JavaScript, creación de índices rápidos, pruebas de igualdad profunda, etc.

- Di las personas que lo hicieron

Recientemente comencé a usarlo específicamente para la zip()función y ha dejado una gran primera impresión. Estoy usando jQuery y CoffeeScript, y simplemente combina perfectamente con ellos. El guión bajo comienza justo donde lo dejan y hasta ahora no me ha decepcionado. Ah, por cierto, solo son 3kb minificados.

Echale un vistazo:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
Brandon
fuente
3
Cuando usas Underscore, te sientes un poco más cerca de la claridad y la comodidad lógica de Haskell.
CamilB
12
En lugar de subrayar, intente esto: lodash.com : reemplazo directo , el mismo gran sabor, más funciones, más consistencia entre navegadores, mejor rendimiento. Consulte kitcambridge.be/blog/say-hello-to-lo-dash para obtener una descripción.
Merlyn Morgan-Graham
16

Además de la excelente y completa respuesta de ninjagecko, todo lo que se necesita para comprimir dos matrices JS en una "imitación de tupla" es:

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

Explicación:
Dado que Javascript no tiene un tuplestipo, las funciones para tuplas, listas y conjuntos no tenían una alta prioridad en la especificación del lenguaje.
De lo contrario, se puede acceder de manera directa a un comportamiento similar a través del mapa de matriz en JS> 1.6 . (en maprealidad, los fabricantes de motores JS a menudo lo implementan en muchos motores> JS 1.4, a pesar de que no se especifica).
La principal diferencia de Python zip, izip... Resultados de mapestilo funcional 's, ya que maprequiere una función de argumento. Además, es una función de la Arrayinstancia. Uno puede usar Array.prototype.mapen su lugar, si una declaración adicional para la entrada es un problema.

Ejemplo:

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

Resultado:

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

Rendimiento relacionado:

Usando mapover- forloops:

Ver: ¿Cuál es la forma más eficiente de fusionar [1,2] y [7,8] en [[1,7], [2,8]]

pruebas zip

Nota: los tipos base como falsey undefinedno poseen una jerarquía de objetos prototípicos y, por lo tanto, no exponen una toStringfunción. Por lo tanto, estos se muestran como vacíos en la salida.
Como parseIntel segundo argumento es la base / número de la raíz, a la cual convertir el número, y dado que mappasa el índice como el segundo argumento a su función de argumento, se utiliza una función de contenedor.

Lorenz Lo Sauer
fuente
Su primer ejemplo dice "aIn no es una función" cuando lo intento. Funciona si llamo .map desde la matriz en lugar de como un prototipo: aIn.map(function(e, i) {return [e, aOut[i]];})¿Qué está mal?
Noumenon el
1
@Noumenon, Array.prototype.mapdebería haber sido Array.prototype.map.call, arregló la respuesta.
usuario
11

Ejemplo moderno de ES6 con un generador:

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

Primero, obtenemos una lista de iterables como iterators. Esto generalmente ocurre de manera transparente, pero aquí lo hacemos explícitamente, ya que cedemos paso a paso hasta que uno de ellos se agote. Verificamos si alguno de los resultados (usando el .some()método) en la matriz dada está agotado, y si es así, rompemos el ciclo while.

Dimitris
fuente
Esta respuesta podría usar más explicación.
cmaher
1
Obtenemos una lista de iteradores de iterables. Esto generalmente ocurre de manera transparente, aquí lo hacemos explícitamente, a medida que cedemos paso a paso hasta que uno de ellos está agotado. Compruebe si alguno de ellos (el método .some ()) en la matriz está agotado e interrumpimos si es así.
Dimitris
11

Junto con otras funciones similares a Python, pythonicofrece una zipfunción, con el beneficio adicional de devolver una evaluación diferida Iterator, similar al comportamiento de su contraparte de 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))];

Divulgación Soy autor y mantenedor de Pythonic

Keyvan
fuente
7

Python tiene dos funciones: zip e itertools.zip_longest. La implementación en JS / ES6 es así:

Implementación del zip de Python en 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])));
};

Resultados:

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

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

[]

Implementación zip_longest de Python en 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
        )
    );
};

Resultados:

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

[[1, 667, 111, indefinido], [2, falso, indefinido, indefinido],
[3, -378, indefinido, indefinido], ['a', '337', indefinido, indefinido]]

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

[[1, 667, 111, nulo], [2, falso, nulo, nulo], [3, -378, nulo, nulo], ['a', '337', nulo, nulo]]

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 ',' No es ninguno ',' No es ninguno ']]

PADYMKO
fuente
4

Puede hacer que la utilidad funcione utilizando 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]]

Sin embargo, en la solución anterior, la longitud de la primera matriz define la longitud de la matriz de salida.

Aquí está la solución en la que tienes más control sobre ella. Es un poco complejo pero 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 ]]

Bhargav Patel
fuente
4

1. Módulo Npm: zip-array

Encontré un módulo npm que se puede usar como una versión javascript de python zip :

conjunto de zip : un equivalente de JavaScript de la función zip de Python. Fusiona los valores de cada una de las matrices.

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

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

Otra opción alternativa es para los usuarios de Tensorflow.js: si necesita una zipfunción en python para trabajar con conjuntos de datos de tensorflow en Javascript, puede usartf.data.zip() en Tensorflow.js.

tf.data.zip () en Tensorflow.js documentado aquí

Huan
fuente
3

No está integrado en Javascript en sí. Algunos de los marcos JavaScript comunes (como Prototype) proporcionan una implementación, o puede escribir la suya propia.

Ámbar
fuente
1
¿Enlace? Además, estaría más interesado si jQuery lo hiciera, ya que eso es lo que estoy usando ...
pq.
2
Sin embargo, tenga en cuenta que jQuery one se comporta de manera ligeramente diferente que Python, ya que devuelve un objeto, no una matriz ... y, por lo tanto, no puede comprimir más de 2 listas juntas.
Ámbar
Correcto, el autor no debería llamar a jQuery one un equivalente.
pq.
3

Al igual que @Brandon, recomiendo la función zip de Underscore . Sin embargo, actúa como , agregando valores según sea necesario para devolver algo de la longitud de la entrada más larga.zip_longestundefined

Utilicé el mixinmétodo para extender el guión bajo con a zipShortest, que actúa como Python zip, basado en la propia fuentezip de la biblioteca .

Puede agregar lo siguiente a su código JS común y luego llamarlo como si fuera parte del guión bajo: _.zipShortest([1,2,3], ['a'])retornos [[1, 'a']], por ejemplo.

// 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;
}});
Palmadita
fuente
¿Voto sin comentario? Estoy feliz de mejorar esta respuesta, pero no puedo sin comentarios.
Pat
2

Puede reducir la matriz de matrices y asignar una nueva matriz tomando el resultado del índice de la matriz interna.

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

Nina Scholz
fuente
1

Una variación de la solución de generador diferido :

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

Y este es el idioma clásico del "grupo n" de Python zip(*[iter(a)]*n):

triples = [...zip(...Array(3).fill(iter(a)))]
georg
fuente
Me pregunto qué le pasa a este, escribí exactamente el mismo. Para mí, se siente mucho mejor que todos los demás, pero tal vez ambos estamos equivocados ... Estaba buscando una manera de agregarle tipos de flujo, pero estoy luchando: D.
cglacet
0

La biblioteca Mochikit proporciona esta y muchas otras funciones similares a Python. El desarrollador de Mochikit también es un fanático de Python, por lo que tiene el estilo general de Python, y también envuelve las llamadas asíncronas en un marco retorcido.

Keith
fuente
0

Intenté esto en JS puro preguntándome cómo los complementos publicados anteriormente hicieron el trabajo. Aquí está mi resultado. Prefiero esto diciendo que no tengo idea de cuán estable será esto en IE y similares. Es solo una maqueta rápida.

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

No es a prueba de balas, pero sigue siendo interesante.

Mr. Polywhirl
fuente
jsfiddle se ve bien. Tiene un botón TidyUp! El botón Ejecutar no mostró la salida de console.log en el panel Resultado. ¿Por qué?
pq.
Se necesita (console.log) algo como Firebug para ejecutarse. Solo cambia console.loga alert.
¿Para qué sirve el panel de resultados?
pq.
Muestra el HTML del violín. En este caso, solo estoy haciendo JS directo. Aquí está el resultado usando document.write() jsfiddle.net/PyTWw/5
-1

Esto elimina una línea de la respuesta basada en iterador 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);
  }
}
Steven Kalt
fuente
-1

Si está bien con ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
T.Chmelevskij
fuente