Javascript相当于Python's zip函数

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]]
113613 次浏览

不是Javascript本身内置的。一些常见的Javascript框架(如Prototype)提供了一个实现,或者你也可以自己编写。

Mochikit库提供了这个函数和许多其他类似python的函数。Mochikit的开发者也是一个Python爱好者,所以它具有Python的一般风格,并且还将异步调用包装在一个扭曲的框架中。

我在纯JS中尝试了一下,想知道上面发布的插件是如何完成这项工作的。这是我的结果。首先我要说的是,我不知道这在IE和类似的软件中会有多稳定。这只是一个快速的模型。

.
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;
}

虽然不是无懈可击,但还是很有趣。

查看下划线库。

Underscore提供了超过100个函数,这些函数既支持你最喜欢的日常函数帮助:映射、过滤器、调用——也支持更专业的功能:函数绑定、javascript模板、创建快速索引、深度等式测试等等。

-说说制作它的人

我最近开始专门为zip()函数使用它,它给我留下了很好的第一印象。我使用jQuery和CoffeeScript,它只是完美地与他们。下划线就在他们离开的地方,到目前为止,它还没有让我失望。哦,顺便说一下,它只缩小了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 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.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数组推导式或生成器。)

像@Brandon一样,我推荐下划线邮政编码函数。然而,它的作用类似于zip_longest,根据需要追加undefined值以返回最长输入长度的值。

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

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

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

除了ninjagecko出色而全面的回答外,将两个js数组压缩成“元组模拟”所需要的是:

//Arrays: aIn, aOut
Array.prototype.map.call( aIn, function(e,i){return [e, aOut[i]];})
< p > 解释: < br > 由于Javascript没有tuples类型,元组、列表和集的函数在语言规范中不是高优先级 否则,类似的行为可以通过数组映射在JS >1.6直接访问。(map实际上经常由JS引擎制造商在许多>JS 1.4引擎中实现,尽管没有指定) 与Python的zipizip,…结果来自map的函数风格,因为map需要一个函数参数。此外,它是Array-instance的函数。如果对输入的额外声明有问题,则可以使用Array.prototype.map代替

例子:

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

结果:

//'('+_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)

相关的性能:

for-loops上使用map:

看:将[1,2]和[7,8]合并为[[1,7],[2,8]的最有效方法是什么?

zip tests

基本类型,如falseundefined不具有原型对象层次结构,因此不公开toString函数。因此,这些在输出中显示为空 由于parseInt的第二个参数是要将数字转换为的基数/数字基数,并且由于map将索引作为第二个参数传递给其参数-函数,因此使用包装函数

Python有两个压缩序列的函数:zip和itertools.zip_longest。Javascript中相同功能的实现如下所示:

Python的zip在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])));
};

结果:

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']
));
< p >[[1、667、111],[2,假的,212年],[3、-378、323],[' a ', '337', 433]]

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

[]

Python的zip_longest在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
)
);
};

结果:

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,

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],
[]
));
< p >[[1, 667, 111, '没有'],[2假'没有','没有'],
[3, -378,“没有”,“没有 ' ], [ ' ”、“337”、“没有”、“ None']]

带有生成器的现代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()方法)是否已耗尽,如果是,则中断while循环。

这将从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);
}
}

惰性发电机解决方案的变体:

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

与其他类python函数一样,pythonic提供了一个zip函数,其额外的好处是返回一个惰性求值的Iterator,类似于它的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的作者和维护者

如果你喜欢ES6:

const zip = (arr,...arrs) =>(
arr.map(
(v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))

您可以减少数组的数组,并通过获取内部数组的索引的结果来映射新数组。

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

有趣的传播。

const
transpose = (r, a) => a.map((v, i) => [...(r[i] || []), v]),
array1 = [1, 2, 3],
array2 = ['a','b','c'],
array3 = [4, 5, 6],
transposed = [array1, array2, array3].reduce(transpose, []);


console.log(transposed);

1. Npm模块:zip-array

我发现了一个npm模块,可以用作python zip的javascript版本:

zip-array - javascript中Python的zip函数。将每个数组的值合并在一起。

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

2. Tensorflow.js中的tf.data.zip()

tensorflow .js用户的另一个替代选择是:如果你需要python中的zip函数来处理Javascript中的tensorflow数据集,你可以在tensorflow .js中使用tf.data.zip()

Tensorflow.js中的tf.data.zip ()文档在在这里

你可以使用ES6来创建实用函数。

console.json = obj => console.log(JSON.stringify(obj));


const zip = (arr, ...arrs) =>
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.json(zip(array1, array2));         // [[1,"a"],[2,"b"],[3,"c"]]
console.json(zip(array1, array2, array3)); // [[1,"a",4],[2,"b",5],[3,"c",6]]

但是,在上述解决方案中,第一个数组的长度定义了输出数组的长度。

这里有一个解决方案,你可以更好地控制它。这有点复杂,但值得。

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

ES2020最短变体:

function * zip(arr1, arr2, i = 0) {
while(arr1[i] || arr2[i]) yield [arr1[i], arr2[i++]].filter(x => !!x);
}
    

[ ...zip(arr1, arr2) ]  // result

原始答案(见下文更新)

我修改了flm的漂亮的回答,以接受任意数量的数组:

 function* zip(arrays, i = 0) {
while (i<Math.min(...arrays.map(({length})=>length))) {
yield arrays.map((arr, j) => arr[j < arrays.length - 1 ? i : i++])
}
}

更新后的答案

正如汤姆·波尔所指出的,此函数不能处理中值为假值的数组。下面是一个更新/改进的版本,可以处理任何类型和长度不等的数组:

 function* zip(arrays, i = 0) {
while (i<Math.min(...arrays.map(arr=>arr.length))) {
yield arrays.map((arr, j) => arr[j < arrays.length - 1 ? i : i++])
}
}
     

const arr1 = [false,0,1,2]
const arr2 = [100,null,99,98,97]
const arr3 = [7,8,undefined,"monkey","banana"]


console.log(...zip([arr1,arr2,arr3]))

我创建了一个简单的函数,通过一个选项来提供一个拉链函数

function zip(zipper, ...arrays) {
if (zipper instanceof Array) {
arrays.unshift(zipper)
zipper = (...elements) => elements
}


const length = Math.min(...arrays.map(array => array.length))
const zipped = []


for (let i = 0; i < length; i++) {
zipped.push(zipper(...arrays.map(array => array[i])))
}


return zipped
}

https://gist.github.com/AmrIKhudair/4b740149c29c492859e00f451832975b

没有等价的函数。如果你只有几个数组,你应该使用for循环来获取一个索引,然后使用索引来访问数组:

var array1 = [1, 2, 3];
var array2 = ['a','b','c'];


for (let i = 0; i < Math.min(array1.length, array2.length); i++) {
doStuff(array1[i], array2[i]);
}

如果你有更多的数组,你可以在数组上有一个内循环。

python zip函数的生成器方法。

function* zip(...arrs){
for(let i = 0; i < arrs[0].length; i++){
a = arrs.map(e=>e[i])
if(a.indexOf(undefined) == -1 ){yield a }else{return undefined;}
}
}
// use as multiple iterators
for( let [a,b,c] of zip([1, 2, 3, 4], ['a', 'b', 'c', 'd'], ['hi', 'hello', 'howdy', 'how are you']) )
console.log(a,b,c)


// creating new array with the combined arrays
let outputArr = []
for( let arr of zip([1, 2, 3, 4], ['a', 'b', 'c', 'd'], ['hi', 'hello', 'howdy', 'how are you']) )
outputArr.push(arr)

我不是javascript的人,但我觉得这些答案中的许多都是试图使用Array.map找到最可爱和最聪明的解决方案,这很好,但对于像我这样不每天使用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的人,所以这些不是最优雅的解决方案,但它们对我来说是可读的。

下面是一个快速有效的方法,使用iter-ops库,操作符邮政编码:

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 ]

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


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

这是我的解决方案

let zip = (a, b) => (a.length < b.length
? a.map((e, i) => [e, b[i]])
: b.map((e, i) => [a[i], e]))