2011-01-31 16 views
128

¿Hay un equivalente en JavaScript de la función zip de Python? Es decir, dado que dos matrices de igual longitud crean una matriz de pares.Javascript equivalente a la función zip de Python

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

Es justo decir que los programadores de Python estamos "temerosos" de los métodos tontos que implican bucles porque son lentos, y por lo tanto, siempre buscamos métodos integrados para hacer las cosas. Pero eso en Javascript deberíamos continuar y escribir nuestros loops porque no son particularmente lentos. – LondonRob

+2

@LondonRob Un bucle es un bucle, oculto detrás de un método "rápido" o no. JavaScript definitivamente ha estado recibiendo más soporte para las funciones de orden superior, con la introducción de Array 'forEach',' reduce', 'map',' every', etc. Simplemente fue el caso de que 'zip' no" hiciera cut "(también está ausente un 'flatMap'), no por consideraciones de rendimiento, pero para ser justos, .NET (3.5) no tenía un Zip en Enumerable por un par de años. Cualquier biblioteca 'funcional' como guión bajo/lodash (lodash 3.x tiene evaluación de secuencia diferida) proporcionará una función zip equivalente. – user2864740

+0

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

Respuesta

3

No incorporada a sí Javascript. Algunos de los marcos de JavaScript comunes (como Prototype) proporcionan una implementación, o puede escribir uno propio.

+1

Enlace? Además, estaría más interesado si jQuery lo hizo, ya que eso es lo que estoy usando ... –

+2

jQuery: http://plugins.jquery.com/project/zip Prototipo: http://www.prototypejs.org/api/enumerable/zip – Amber

+1

Sin embargo, tenga en cuenta que el jQuery one se comporta de forma ligeramente diferente que el de Python, ya que devuelve un objeto, no una matriz ... y por lo tanto no puede comprimir más de 2 listas juntas. – Amber

0

La biblioteca Mochikit proporciona esta y muchas otras funciones parecidas a las de 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 asincrónicas en un marco retorcido.

0

Eché un vistazo a esto en JS puro preguntándome cómo los plugins publicados anteriormente hicieron el trabajo. Aquí está mi resultado. Voy a prologar 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; 
 
}

que no es a prueba de balas, pero sigue siendo interesante.

+0

jsfiddle se ve bien. Tiene un botón TidyUp! El botón Ejecutar no mostró la salida de console.log en el panel de resultados. ¿Por qué? –

+0

(console.log) requiere algo como Firebug para ejecutarse. Simplemente cambie 'console.log' a' alert'. –

+0

¿Para qué sirve el panel de resultados? –

25

Consulte la biblioteca Underscore.

Underscore provides over 100 functions that support both your favorite workaday functional helpers: map, filter, invoke — as well as more specialized goodies: function binding, javascript templating, creating quick indexes, deep equality testing, and so on.

- dicen que la gente que lo hizo

recientemente he empezado a utilizar específicamente para la función zip() y se ha dejado una gran primera impresión. Estoy usando jQuery y CoffeeScript, y funciona perfectamente con ellos. El subrayado se inicia justo donde lo dejan y hasta ahora no me ha decepcionado. Ah, por cierto, es solo 3kb minificado.

Échale un vistazo.

+3

Al usar subrayado, te sientes un poco más cerca de la claridad y la comodidad lógica de Haskell. – CamilB

+9

En lugar de subrayado, intente esto: http://lodash.com/ - reemplazo inmediato, mismo gran sabor, más funciones, más consistencia entre navegadores, mejor rendimiento. Ver http://kitcambridge.be/blog/say-hello-to-lo-dash/ para una descripción. –

123

2016 actualización:

Aquí hay una versión snazzier EcmaScript 6:

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

Ilustración:

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

(y FizzyTea señala que ES6 tiene una sintaxis argumento variadic, por lo que la siguiente actuará como Python, pero vea a continuación la exención de responsabilidad ... esto no será su propio inverso así que zip(zip(x)) no será igual x :)

> 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 

(Ten en cuenta que la sintaxis ... puede tener problemas de rendimiento en este momento, y posiblemente en el futuro, por lo que si se utiliza la segunda respuesta con argumentos variadic, es posible que desee prueba perf.)


Aquí hay una 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(...) 

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

A continuación se imitan el comportamiento de Python zip en casos extremos, donde las matrices no son de igual tamaño, en silencio simulando 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 comportamiento de Python itertools.zip_longest, insertando undefined, donde las matrices no están definidos:

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 estos dos última versión (. variadic aka-versiones de argumento múltiple), entonces zip ya no es su propio inverso. Para imitar la expresión zip(*[...]) de Python, deberá hacer zip.apply(this, [...]) cuando desee invertir la función zip o si desea tener una cantidad variable de listas como entrada.


adición:

Para que esto sea manejar cualquier iterable (por ejemplo en Python puede utilizar zip en cadenas, oscila, los objetos de mapa, etc.), se podría definir lo siguiente:

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

sin embargo, si usted escribe zip en el siguiente way, 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]}) 
    }); 
} 

Demostración:

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

(O usted podría utilizar una función range(...) de estilo de Python si has uno ya escrito. Eventualmente podrá usar compilaciones o generadores ECMAScript.)

+1

Esto no funciona para mí: TypeError: Object 1 no tiene el método 'map' –

+3

Y ES6 para varios argumentos y cualquier iterable: 'zip = (... rows) => [... rows [0]]. Map ((_, c) => rows.map (fila => fila [c])); ' – 1983

+1

@FizzyTea: eso es maravilloso, gracias, lo incluiré – ninjagecko

1

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

I utilizó el método mixin extender subrayado con una zipShortest, que actúa como Python de zip, basado apagado de the library's own source for zip.

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']) devuelve [[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; 
}}); 
+0

Votación sin comentarios? Me complace mejorar esta respuesta, pero no puedo sin comentarios. – Pat

10

Además de una excelente y completa respuesta de ninjagecko, todo lo que necesita para comprimir dos JS-arrays en un "tupla-mímica" es:

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

Explicación:
Desde Javascript doesn No tiene un tipo de tuples, las funciones para tuplas, listas y conjuntos no eran una prioridad en la especificación del idioma.
De lo contrario, un comportamiento similar es accesible de manera directa a través de Array map in JS >1.6. (map en realidad es implementado por los fabricantes de motores JS en muchos motores> JS 1.4, a pesar de que no se especifica).
La mayor diferencia con zip de Python, izip, ... resulta del estilo funcional map, ya que map requiere un argumento de función. Además, es una función de Array -instance. Uno puede usar Array.prototype.map en 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) 

de rendimiento relacionados con:

Usando map más for -loops:

Ver: What is the most efficient way of merging [1,2] and [7,8] into [[1,7], [2,8]]

zip tests

Nota: los tipos base como false y undefined qué no posee un objeto de jerarquía de prototipos y por lo tanto no exponga una función toString. Por lo tanto, estos se muestran como vacíos en la salida.
Como el segundo argumento de parseInt es la raíz de base/número, a la que convertir el número, y dado que map pasa el índice como el segundo argumento a su función de argumento, se utiliza una función de envoltura.

+0

Su primer ejemplo dice "aIn no es una función" cuando lo intento. Funciona si llamo .map desde la matriz en lugar de como prototipo: 'aIn.map (función (e, i) {return [e, aOut [i]];})' ¿Qué pasa? – Noumenon

+1

@Noumenon, 'Array.prototype.map' debería haber sido' Array.prototype.map.call', corrigió la respuesta. – user

3

El Python tiene dos funciones: zip e itertools.zip_longest.Aplicación sobre JS/ES6 es así:

Python`s implementación de ZIP 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, false, 212 ], [ 3, -378, 323 ], [ 'a', '337', 433 ] ]

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

[]

Implementación Python`s zip_longest 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, undefined ], [ 2, false, undefined, undefined ],
[ 3, -378, undefined, undefined ], [ 'a', '337', undefined, undefined ] ]

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

2

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

En primer lugar, obtener una lista de iterables como iterators. Esto generalmente ocurre de forma transparente, pero aquí lo hacemos explícitamente, ya que cedemos paso a paso hasta que uno de ellos se agote. Comprobamos si alguno de los resultados (utilizando el método .some()) en la matriz dada se ha agotado, y si es así, rompemos el ciclo while.

+0

Esta respuesta podría usar más explicación. – cmaher

+1

Obtenemos una lista de iteradores de iterables. Esto generalmente ocurre de forma transparente, aquí lo hacemos explícitamente, ya que cedemos paso a paso hasta que uno de ellos se agote. Comprueba si alguno de ellos (el método .some()) en la matriz está agotado y lo interrumpimos si es así. – Ddi

0

Esto se afeita una línea fuera a base de iterador respuesta Ddi 's:

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

Otra variación de Ddi's:

function* iter(it) { 
 
    for (let x of it) 
 
     yield x; 
 
} 
 

 
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 esta es la forma de escribir el clásico de py zip(*[iter(a)]*n):

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