Is there a javascript equivalent of Python's zip function? That is, given multiple arrays of equal lengths create an array of pairs.
For instance, if I have three arrays that look like this:
var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
var array3 = [4, 5, 6];
The output array should be:
var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]
2016 update:
Here's a snazzier Ecmascript 6 version:
zip= rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))
Illustration equiv. to Python{zip(*args)
}:
> zip([['row0col0', 'row0col1', 'row0col2'],
['row1col0', 'row1col1', 'row1col2']]);
[["row0col0","row1col0"],
["row0col1","row1col1"],
["row0col2","row1col2"]]
(and FizzyTea points out that ES6 has variadic argument syntax, so the following function definition will act like python, but see below for disclaimer... this will not be its own inverse so zip(zip(x))
will not equal x
; though as Matt Kramer points out zip(...zip(...x))==x
(like in regular python zip(*zip(*x))==x
))
Alternative definition equiv. to 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
(Do note that the ...
syntax may have performance issues at this time, and possibly in the future, so if you use the second answer with variadic arguments, you may want to perf test it.)
Here's a 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(...)
The above assumes that the arrays are of equal size, as they should be. It also assumes you pass in a single list of lists argument, unlike Python's version where the argument list is variadic. If you want all of these "features", see below. It takes just about 2 extra lines of code.
The following will mimic Python's zip
behavior on edge cases where the arrays are not of equal size, silently pretending the longer parts of arrays don't exist:
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()
// []
This will mimic Python's itertools.zip_longest
behavior, inserting undefined
where arrays are not defined:
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()
// []
If you use these last two version (variadic aka. multiple-argument versions), then zip is no longer its own inverse. To mimic the zip(*[...])
idiom from Python, you will need to do zip.apply(this, [...])
when you want to invert the zip function or if you want to similarly have a variable number of lists as input.
addendum:
To make this handle any iterable (e.g. in Python you can use zip
on strings, ranges, map objects, etc.), you could define the following:
function iterView(iterable) {
// returns an array equivalent to the iterable
}
However if you write zip
in the following way, even that won't be necessary:
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]]
(Or you could use a range(...)
Python-style function if you've written one already. Eventually you will be able to use ECMAScript array comprehensions or generators.)