Javascript equivalente à função zip do Python


215

Existe um equivalente javascript da função zip do Python? Ou seja, dadas várias matrizes de comprimentos iguais, cria uma matriz de pares.

Por exemplo, se eu tiver três matrizes parecidas com esta:

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

A matriz de saída deve ser:

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

5
É justo dizer que nós, programadores Python, temos "medo" de métodos burros que envolvem loops porque são lentos e, portanto, sempre procuramos métodos internos para fazer as coisas. Mas que em Javascript devemos continuar e escrever nossos loops porque eles não são particularmente lentos?
precisa saber é o seguinte

3
@LondonRob Um loop é um loop, escondido atrás de um método 'rápido' ou não. JavaScript foi definitivamente ficando mais suporte para funções de ordem superior, com a introdução da matriz de forEach, reduce, map, every, etc. Era apenas o caso que zipnão "fazer o corte" (a flatMaptambém está ausente), e não para as considerações de desempenho - mas para ser justo, o .NET (3.5) não tinha um Zip no Enumerable por alguns anos! Qualquer biblioteca 'funcional' como o sublinhado / lodash (o lodash 3.x possui uma avaliação de sequência lenta) fornecerá uma função zip equivalente.
user2864740

@ user2864740 Um loop interpretado (como em Python) sempre será muito mais lento que um loop de código de máquina. Um loop compilado por JIT (como nos mecanismos JS modernos) pode se aproximar da velocidade da CPU nativa, tanto que o ganho introduzido pelo uso de um loop de código de máquina pode ser compensado pela sobrecarga da chamada de função anônima. Ainda assim, faz sentido ter essas funções internas e criar um perfil de várias variações de seus "loops internos" com vários mecanismos JS. Os resultados podem não ser óbvios.
26415 Tobia

Respostas:


177

Atualização de 2016:

Aqui está uma versão mais ecológica do Ecmascript 6:

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

Ilustração equiv. para Python { zip(*args)}:

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

(e o FizzyTea salienta que o ES6 tem sintaxe de argumento variada, portanto a seguinte definição de função funcionará como python, mas veja abaixo o aviso de isenção de responsabilidade ... isso não será o seu próprio inverso, portanto zip(zip(x))não será igual x; embora, como Matt Kramer aponte zip(...zip(...x))==x(como em python comum zip(*zip(*x))==x))

Definição alternativa equiv. para 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

(Observe que a ...sintaxe pode ter problemas de desempenho no momento e possivelmente no futuro; portanto, se você usar a segunda resposta com argumentos variados, poderá testá-la com perfeição.)


Aqui está um 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(...)

O exposto acima pressupõe que as matrizes sejam do mesmo tamanho, como deveriam ser. Também assume que você passa em um único argumento da lista de listas, diferente da versão do Python, onde a lista de argumentos é variável. Se você deseja todos esses "recursos", veja abaixo. São necessárias apenas duas linhas de código extras.

A seguir, imitaremos o zipcomportamento do Python em casos extremos onde as matrizes não são do mesmo tamanho, fingindo silenciosamente que as partes mais longas das matrizes não existem:

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

Isso imitará o itertools.zip_longestcomportamento do Python , inserindo undefinedonde as matrizes não estão 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()
// []

Se você usar essas duas últimas versões (variadic, também conhecida como versões com vários argumentos), o zip não será mais o seu próprio inverso. Para imitar o zip(*[...])idioma do Python, você precisará fazer isso zip.apply(this, [...])quando quiser inverter a função zip ou se desejar ter um número variável de listas como entrada.


adendo :

Para tornar esse manipulador iterável (por exemplo, em Python, você pode usar zipem strings, intervalos, objetos de mapa etc.), você pode definir o seguinte:

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

No entanto, se você escrever zipda seguinte maneira , mesmo isso não será necessário:

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

(Ou você pode usar uma range(...)função Python-estilo, se você escreveu um já. Eventualmente, você será capaz de usar compreensões de matriz ECMAScript ou geradores).


1
Isso não funciona para mim: TypeError: O objeto 1 não possui o método 'map'
Emanuele Paolini

7
E ES6 para args variados e qualquer iterável:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983

o "Objeto 1 não possui método 'mapa'" provavelmente é o caso de tentar usá-lo em um objeto que não possui um método de mapa (como um nodelist ou uma string) que foi abordado no adendo desta postagem
Ninjagecko

Embora seja verdade que a versão variável do ES6 não preserva zip(zip(x)) = x, você ainda pode aproveitar a confiança disso zip(...zip(...x)) = x.
Matt Kramer

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

34

Confira a biblioteca Underscore .

O Underscore fornece mais de 100 funções que oferecem suporte a seus auxiliares funcionais favoritos: mapear, filtrar, chamar - e também itens mais especializados: ligação de funções, modelagem de javascript, criação de índices rápidos, testes de igualdade profunda e assim por diante.

- Diga as pessoas que fizeram isso

Recentemente, comecei a usá-lo especificamente para a zip()função e deixou uma ótima primeira impressão. Estou usando jQuery e CoffeeScript, e ele combina perfeitamente com eles. Sublinhado começa exatamente onde eles param e até agora não me decepcionou. Ah, a propósito, é apenas 3kb minificado.

Confira:

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

3
Ao usar o Underscore, você se sente um pouco mais próximo da clareza e conforto lógico de Haskell.
CamilB

12
Em vez de sublinhado, tente o seguinte: lodash.com - substituição suspensa, mesmo sabor excelente, mais recursos, mais consistência entre navegadores, melhor desempenho. Consulte kitcambridge.be/blog/say-hello-to-lo-dash para obter uma descrição.
Merlyn Morgan-Graham

16

Além da excelente e abrangente resposta do ninjagecko, basta compactar duas matrizes JS em uma "imitação de tupla" é:

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

Explicação:
Como o Javascript não possui um tuplestipo, as funções para tuplas, listas e conjuntos não eram uma alta prioridade na especificação do idioma.
Caso contrário, um comportamento semelhante é acessível de maneira direta via mapa de matriz em JS> 1.6 . ( mapna verdade, é frequentemente implementado pelos fabricantes de mecanismos JS em muitos> motores JS 1.4, apesar de não especificado).
A principal diferença para Python zip, izip... resultados de mapestilo funcional 's, uma vez que maprequer uma função-argumento. Além disso, é uma função da Arrayinstância. Pode-se usar Array.prototype.map, se uma declaração extra para a entrada for um problema.

Exemplo:

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

Desempenho relacionado:

Usando mapover- forloops:

Veja: Qual é a maneira mais eficiente de mesclar [1,2] e [7,8] em [[1,7], [2,8]]

testes de zip

Nota: os tipos de base como falsee undefinednão possuem uma hierarquia de objetos prototípica e, portanto, não expõem uma toStringfunção. Portanto, eles são mostrados como vazios na saída.
Como parseInto segundo argumento é a base / número da raiz, para a qual converter o número e como mappassa o índice como o segundo argumento para sua função de argumento, uma função de wrapper é usada.


Seu primeiro exemplo diz "aIn não é uma função" quando eu tento. Funciona se eu chamar .map da matriz em vez de protótipo: aIn.map(function(e, i) {return [e, aOut[i]];})O que está errado?
Noumenon

1
@ Noumenon, Array.prototype.mapdeveria ter sido Array.prototype.map.call, corrigiu a resposta.
usuário

11

Exemplo moderno do ES6 com um gerador:

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

Primeiro, obtemos uma lista de iteráveis ​​como iterators. Isso geralmente acontece de forma transparente, mas aqui o fazemos explicitamente, pois produzimos passo a passo até que um deles se esgote. Verificamos se algum dos resultados (usando o .some()método) no array especificado está esgotado e, se sim, interrompemos o loop while.


Esta resposta poderia usar mais explicações.
Cmaher 17/01/19

1
Nós obtemos uma lista de iteradores de iterables. Isso geralmente acontece de forma transparente, aqui fazemos explicitamente, à medida que produzimos passo a passo até que um deles se esgote. Verifique se algum deles (o método .some ()) na matriz está esgotado e interrompemos se for o caso.
Dimitris

11

Juntamente com outras funções do tipo Python, pythonicoferece uma zipfunção, com o benefício extra de retornar uma avaliação lenta Iterator, semelhante ao comportamento de sua contraparte do 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))];

Divulgação Sou autor e mantenedor de Pythonic


7

O Python tem duas funções: zip e itertools.zip_longest. A implementação em JS / ES6 é assim:

Implementação do zip do Python no 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],
    []
));

[]

Implementação do zip_longest do Python no 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, 'Não é nenhum'], [2, falso, 'Não é nenhum', 'Não é nenhum'],
[3, -378, 'Não é nenhum', 'Não é nenhum'], ['a ',' 337 ',' Não é nenhum ',' Não é nenhum ']]


4

Você pode fazer a função de utilitário usando o 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]]

No entanto, no comprimento da solução acima da primeira matriz define o comprimento da matriz de saída.

Aqui está a solução na qual você tem mais controle sobre ela. É um pouco complexo, mas vale a 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. Módulo Npm: zip-array

Encontrei um módulo npm que pode ser usado como uma versão javascript do python zip:

zip-array - Um equivalente javascript da função zip do Python. Mescla os valores de cada uma das matrizes.

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

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

Outra opção alternativa é para os usuários do Tensorflow.js: se você precisar de uma zipfunção em python para trabalhar com conjuntos de dados do tensorflow em Javascript, poderá usartf.data.zip() no Tensorflow.js.

tf.data.zip () no Tensorflow.js documentado aqui


3

Não é incorporado ao próprio Javascript. Algumas das estruturas Javascript comuns (como Prototype) fornecem uma implementação, ou você pode escrever sua própria.


1
Ligação? Além disso, eu estaria mais interessado se o jQuery fizesse isso, já que é isso que estou usando ...
pq.


2
Observe, no entanto, que o jQuery se comporta de maneira um pouco diferente do Python, pois retorna um objeto, não uma matriz ... e, portanto, não pode compactar mais de duas listas.
31411 Amber

Certo, o autor não deve chamar o jQuery de equivalente.
pq.

3

Como o @Brandon, eu recomendo a função zip do Underscore . No entanto, ele age como , acrescentandozip_longestundefined valores conforme necessário para retornar algo do tamanho da entrada mais longa.

Eu usei o mixinmétodo para estender o sublinhado com a zipShortest, que funciona como o Python zip, baseado na fonte da própria bibliotecazip .

Você pode adicionar o seguinte ao seu código JS comum e chamá-lo como se fosse parte do sublinhado: _.zipShortest([1,2,3], ['a'])retorna [[1, 'a']], por exemplo.

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

Voto negativo sem um comentário? Fico feliz em melhorar esta resposta, mas não posso sem feedback.
Pat

2

Você pode reduzir a matriz de matrizes e mapear uma nova matriz obtendo o resultado do índice da 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);


1

Uma variação da solução do gerador lento :

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 este é o idioma clássico "n-group" do python zip(*[iter(a)]*n):

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

Gostaria de saber o que há de errado com este, eu escrevi exatamente o mesmo. Para mim, parece muito melhor do que todos os outros, mas talvez estejamos ambos errados ... Eu estava procurando uma maneira de adicionar tipos de fluxo a ele, mas estou lutando: D.
Cglacet

0

A biblioteca Mochikit fornece essa e muitas outras funções semelhantes ao Python. O desenvolvedor do Mochikit também é um fã de Python, por isso tem o estilo geral do Python e também os envoltórios que o assíncrono chama em uma estrutura distorcida.


0

Eu fiz isso em JS puro, imaginando como os plug-ins postados acima fizeram o trabalho. Aqui está o meu resultado. Eu prefácio isso dizendo que não tenho idéia de quão estável isso será no IE e similares. É apenas uma maquete 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;
}

Não é à prova de balas, mas ainda é interessante.


jsfiddle parece bom. Tem um botão TidyUp! O botão Executar não mostrou a saída do console.log no painel Resultado. Por quê?
pq.

Ele (console.log) requer algo como o Firebug para ser executado. Apenas mude console.logpara alert.

Para que serve o painel Resultados?
pq.

Ele mostra o HTML do violino. Neste caso, eu estou apenas fazendo JS direto. Aqui está o resultado usando document.write() jsfiddle.net/PyTWw/5

-1

Isso raspa uma linha da resposta baseada em iterador do 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 você está bem com o ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.