Javascript中Python zip函数的等效实现
有没有类似于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 个回答
这里有一个现代的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()
方法),如果有的话,就会跳出这个循环。
看看这个库 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]]
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数组推导或生成器。)