javascript中是否有类似于Python的zip函数?也就是说,给定多个相等长度的数组,创建一个由对组成的数组。

例如,如果我有三个这样的数组:

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

输出数组应该是:

var outputArray = [[1,'a',4], [2,'b',5], [3,'c',6]]

当前回答

我不是一个javascript的人,但我觉得这些答案是试图找到最可爱和最聪明的解决方案使用数组。这是很好的,但对于像我这样不每天使用javascript的人来说,这里有一些可能更可读的替代方案。

也许有一种方法可以避免一些可爱而聪明的代码:

function zip(a,b){
    // pre-allocate an array to hold the results 
    rval=Array(Math.max(a.length, b.length));
    for(i=0; i<rval.length; i++){ 
        rval[i]=[a[i],b[i]] 
    }
    return rval
}

如果你喜欢生成器:

function* _zip(a,b){
    len = Math.max(a.length, b.length) // handle different sized arrays
    for(i=0; i<len; i++) { yield [a[i],b[i]] }
}

或者如果你真的想使用Array.map:

function map(a,b){
    x = a.length > b.length ? a : b // call map on the biggest array
    return x.map((_,i)=>[a[i],b[i]])
}

就像我说的,我不是一个日常使用javascript的人,所以这些不是最优雅的解决方案,但它们对我来说是可读的。

其他回答

和@Brandon一样,我推荐Underscore的zip功能。但是,它的作用类似zip_longest,根据需要附加未定义的值以返回最长输入的长度。

我使用mixin方法用zipshort扩展下划线,它的作用类似于Python的zip,基于库自己的zip源代码。

你可以在你的普通JS代码中添加以下代码,然后像调用下划线一样调用它:_。zipShortest ([1, 2, 3], [a])返回[[1,' ']],例如。

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

惰性生成器解决方案的一个变体:

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

这是python经典的“n-group”习语zip(*[iter(a)]*n):

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

下面是一个快速有效的方法,使用iter-ops库,operator zip:

const {pipe, zip} = require('iter-ops');

const i = pipe(array1, zip(array2, array3));

console.log(...i); //=> [ 1, 'a', 4 ] [ 2, 'b', 5 ] [ 3, 'c', 6 ]

标准库将所有输入作为可迭代对象处理,因此它们只迭代一次。它可以以同样的方式处理所有类型的可迭代对象——iterable, AsyncIterable, Iterator, AsyncIterator。


附注:我是iter-ops的作者。

与其他类似Python的函数一样,pythonic提供了一个zip函数,其额外的好处是返回一个惰性求值迭代器,类似于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))];

我是Pythonic的作者和维护者

2016年更新:

下面是一个更时髦的Ecmascript 6版本:

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

对应于Python{zip(*args)}的插图:

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

(FizzyTea指出ES6有可变参数语法,所以下面的函数定义将像python一样,但请参见下面的免责声明…它不是它自己的逆函数所以zip(zip(x))不等于x;尽管正如Matt Kramer指出的那样,zip(…zip(…x))==x(就像常规python中的zip(*zip(*x))==x)))

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

(请注意…语法现在可能存在性能问题,将来也可能存在,因此如果使用带有可变参数的第二个答案,可能需要对其进行性能测试。也就是说,自从它被纳入标准已经有一段时间了。)

如果你想在字符串上使用它,一定要注意它的附录(也许现在用es6 iterables有更好的方法)。


这里有一句话:

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

上面假设数组的大小相等,因为它们应该是相等的。它还假设你传递了一个单独的list of lists参数,不像Python版本的参数列表是可变的。如果你想要所有这些“特性”,请参见下面。它只需要额外的两行代码。

下面将模拟Python在数组大小不相等的边缘情况下的zip行为,默默地假装数组中较长的部分不存在:

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

这将模拟Python的itertools.zip_longest行为,在未定义数组的地方插入undefined:

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

如果你使用这最后两个版本(可变变量aka。多参数版本),那么zip就不再是它自己的逆。要模仿Python中的zip(*[…])习语,需要执行zip。当您想要反转zip函数,或者如果您想类似地使用可变数量的列表作为输入时,应用(this,[…])。


附录:

要使此句柄为任何可迭代对象(例如,在Python中,您可以对字符串、范围、映射对象等使用zip),您可以定义以下内容:

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

然而,如果你以下面的方式写zip,即使这样也不需要:

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

演示:

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

(或者你可以使用一个范围(…)python风格的函数(如果你已经写过的话)。最终,您将能够使用ECMAScript数组推导式或生成器。)