Javascript中Python zip函数的等效实现

311 投票
24 回答
129584 浏览
提问于 2025-04-16 10:58

有没有类似于Python中zip函数的JavaScript功能?也就是说,给定多个长度相同的数组,创建一个包含配对的数组。

比如说,如果我有三个数组,看起来像这样:

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

24 个回答

33

这里有一个现代的ES6示例,使用了生成器:

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

首先,我们得到一组可迭代的对象,称为iterators。通常,这个过程是自动进行的,但在这里我们是明确地一步一步来,直到其中一个对象用完为止。我们会检查给定数组中的结果,看看有没有哪个用完了(使用.some()方法),如果有的话,就会跳出这个循环。

48

看看这个库 Underscore

Underscore 提供了超过 100 个功能,支持你日常工作中常用的功能,比如:map、filter、invoke,还有一些更专业的功能,比如函数绑定、JavaScript 模板、快速创建索引、深度相等测试等等。

– 这是它的开发者说的

我最近开始专门使用它的 zip() 函数,给我留下了很好的第一印象。我在用 jQuery 和 CoffeeScript,它和这两个工具配合得非常好。Underscore 完美地接替了它们的功能,到现在为止没有让我失望。哦,对了,它压缩后只有 3kb。

快去看看吧:

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

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的可迭代对象来实现)。


这里有一个单行代码:

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

上面的代码假设数组的大小是相等的,这应该是这样。它还假设你传入的是一个包含多个列表的单一参数,这与Python的版本不同,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()
// []

如果你使用最后两个版本(可变参数版本),那么zip就不再是它自己的逆操作了。要模拟Python中的 zip(*[...]) 这种用法,当你想要反转zip函数或想要输入多个列表时,你需要使用 zip.apply(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]]

(或者如果你已经写过一个类似 range(...) 的Python风格函数,也可以使用它。最终你将能够使用ECMAScript数组推导或生成器。)

撰写回答