231

I have 2 arrays:

var a = [1, 2, 3]
var b = [a, b, c]

What I want to get as a result is:

[[1, a], [2, b], [3, c]]

It seems simple but I just can't figure out.

I want the result to be one array with each of the elements from the two arrays zipped together.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
userMod2
  • 8,312
  • 13
  • 63
  • 115
  • 3
    Note that array map() is not supported in IE8, if that is a problem. Polyfill here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map – TimHayes Feb 25 '14 at 13:37
  • 17
    This question is NOT a duplicate as it only asks for *2* arrays to be zipped instead of *N* arrays. Thus it is a special case for which there are specialized, more performant solutions. – le_m Jun 04 '16 at 00:52

5 Answers5

350

Use the map method:

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

var c = a.map(function(e, i) {
  return [e, b[i]];
});

console.log(c)

DEMO

Seth
  • 10,198
  • 10
  • 45
  • 68
tewathia
  • 6,890
  • 3
  • 22
  • 27
  • 5
    Total javascript noob here, but I was under the impression that the index has to come before the element? that is, .map(function(index,element))? – runawaykid Oct 29 '16 at 11:05
  • 17
    @runawaykid The answer is correct. You can see the [MDN map documentation on the callback parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Parameters). Params are (in order): `currentvalue`, `index`, `array` – icc97 Jan 06 '17 at 15:22
  • 19
    Using the arrow notation is even a bit nicer: var c = a.map((e, i) => [e, b[i]]); – marczoid Nov 15 '17 at 10:39
  • 1
    How would you create a dictionary/object from this? – Climax Apr 26 '18 at 06:50
  • 1
    @Climax Use forEach instead, e.g., `const obj = Object.create(null); a.forEach((e, i) => obj[e] = b[i]);` – corvec Jun 18 '18 at 23:46
  • There is also the [`.entries` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) which was added to JS in ES2015, with which the expression in your answer can be written as follows: `Array.from(a.entries()).map(([i, e]) => [e, b[i]]);`. This is more verbose, but may be clearer to those who don't realize that `map` takes a second parameter :-) – Abbafei Jul 24 '18 at 20:15
  • @Climax you can also use reduce for this. `const c = a.reduce((accum, cur, i) => ({ ...accum, [cur]: b[i] }), {});` – user2954397 Jan 25 '19 at 22:49
  • return '\n' + array1[i] + ' - ' + array2[i]; ------------ write in this way to line break after every iteration. Check on jsfiddle : http://jsfiddle.net/Ahsan_Aftab/bc7hd258/ – Ahsan Aftab Jan 31 '19 at 08:15
  • I implemented a multi-array zip, but too bad I cannot post it here. I may add it to this answer at a later time. – Mr. Polywhirl Feb 05 '19 at 18:23
  • @runawaykid You (and the others that upvoted your comment) are probably thinking of jQuery's [map](https://api.jquery.com/map/) function, which has that signature (and pre-dates the native JavaScript map function). – Steve Feb 12 '20 at 00:46
  • 4
    Just note that if the arrays are different lengths, then this will either pad the right side with undefined values (if `a` is longer) or will omit some entries (if `b` is longer). – ErikE Feb 19 '20 at 18:56
  • the next answer by @RokoCBuljan is less confusing imo – WestCoastProjects Jun 01 '20 at 03:00
  • 1
    If you have two different lengths array and you want to include all elements always I would recommend to use something like this: https://github.com/gkucmierz/algorithms/blob/master/js/zip.js ```js const zip = (arr1, arr2) => { const zipped = []; const len = Math.max(arr1.length, arr2.length); for (let i = 0; i < len; ++i) { zipped[i] = [arr1[i], arr2[i]]; } return zipped; }; console.log(zip( [1, 2, 3, 4], [...'abc'], )); ``` – gkucmierz Jun 12 '20 at 05:47
127

Zip Arrays of same length:

Using Array.prototype.map()

const zip = (a, b) => a.map((k, i) => [k, b[i]]);

console.log(zip([1,2,3], ["a","b","c"]));
// [[1, "a"], [2, "b"], [3, "c"]]

Zip Arrays of different length:

Using Array.from()

const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]);

console.log( zip([1,2,3], ["a","b","c","d"]) );
// [[1, "a"], [2, "b"], [3, "c"], [undefined, "d"]]

Using Array.prototype.fill() and Array.prototype.map()

const zip = (a, b) => Array(Math.max(b.length, a.length)).fill().map((_,i) => [a[i], b[i]]);

console.log(zip([1,2,3], ["a","b","c","d"]));
// [[1, "a"], [2, "b"], [3, "c"], [undefined, "d"]]

Zip Multiple (n) Arrays:

const zip = (...arr) => Array(Math.max(...arr.map(a => a.length))).fill().map((_,i) => arr.map(a => a[i]));  
console.log(zip([1,2], [3,4], [5,6])); // [[1,3,5], [2,4,6]]
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 1
    Thanks for this. Foruntanely map worked in my environment nevertheless I've recorded this method just in case. – userMod2 Feb 26 '14 at 13:05
  • This `const zip = (arr1, arr2) => arr1.map((k, i) => [k, arr2[i]]);` is concise and elegant – WestCoastProjects Jun 01 '20 at 03:01
  • 2
    Elegant, but faulty. Classic zip must be commutative, this implementation is not. check out with arrays of different length. – goteguru Nov 11 '20 at 10:11
  • 3
    **Thank you** @goteguru, I added two more example for array of different length. – Roko C. Buljan Nov 11 '20 at 18:28
  • 5
    Thx @RokoC.Buljan, nice. However I think it should be Math.min. zip comes from the functional languages (just like filter, map, reduce and friends) and it's main purpose is to feeding two streams into one binary computation. The result is undefined anyway if one of them is missing, therefore we should stop early. Moreover, in FP the streams may be infinite (!) which would mean the above algorithm never stops. Of course it's not the case using js Arrays, but still this is the classic "zip". (Performance wise: imagine one array has 100k items, and the other has only twelve). – goteguru Nov 12 '20 at 20:20
  • even shorter :P `const zip = (a, b) => [...Array(Math.max(b.length, a.length))].map((_, i) => [a[i], b[i]]);` – Igor Sukharev Jan 16 '21 at 07:23
  • **For TS:** `const zip = (a: A1[], b: A2[]) => a.map((k, i) => [k, b[i]] as [A1, A2]);` – Pavlo Maistruk Sep 13 '21 at 16:01
  • 1
    This is the correct answer to the question, and not the one that OP accepted. – Vladimir Despotovic Mar 02 '22 at 11:03
9

Zipping by leveraging generator functions

You can also use a generator function to zip().

const a = [1, 2, 3]
const b = ['a', 'b', 'c']

/**
 * Zips any number of arrays. It will always zip() the largest array returning undefined for shorter arrays.
 * @param  {...Array<any>} arrays 
 */
function* zip(...arrays){
  const maxLength = arrays.reduce((max, curIterable) => curIterable.length > max ? curIterable.length: max, 0);
  for (let i = 0; i < maxLength; i++) {
    yield arrays.map(array => array[i]);
  }
}

// put zipped result in an array
const result = [...zip(a, b)]

// or lazy generate the values
for (const [valA, valB] of zip(a, b)) {
  console.log(`${valA}: ${valB}`);  
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

The above works for any number of arrays and will zip() the longest array so undefined is returned as a value for shorter arrays.

Zipping of all Iterables

Here a function which can be used for all Iterables (e.g. Maps, Sets or your custom Iterable), not just arrays.

const a = [1, 2, 3];
const b = ["a", "b", "c"];

/**
 * Zips any number of iterables. It will always zip() the largest Iterable returning undefined for shorter arrays.
 * @param  {...Iterable<any>} iterables
 */
function* zip(...iterables) {
  // get the iterator of for each iterables
  const iters = [...iterables].map((iterable) => iterable[Symbol.iterator]());
  let next = iters.map((iter) => iter.next().value);
  // as long as any of the iterables returns something, yield a value (zip longest)
  while(anyOf(next)) {
    yield next;
    next = iters.map((iter) => iter.next().value);
  }

  function anyOf(arr){
    return arr.some(v => v !== undefined);
  }
}

// put zipped result in aa array
const result = [...zip(a, new Set(b))];

// or lazy generate the values
for (const [valA, valB] of zip(a, new Set(b))) {
  console.log(`${valA}: ${valB}`);
}

Obviously it would also be possible to just use [...Iterable] to transform any Iterable to an array and then use the first function.

Mushroomator
  • 6,516
  • 1
  • 10
  • 27
4

Using the reduce method:

const a = [1, 2, 3]
const b = ['a', 'b', 'c']

var c = a.reduce((acc, curr, ind) => {
  acc.push([curr, b[ind]]);
  return acc;
}, []);
console.log(c)

With forEach method:

const a = [1, 2, 3]
const b = ['a', 'b', 'c']
const c = [];
a.forEach((el, ind) => {
    c.push([el, b[ind]])
});
console.log(c)
XMehdi01
  • 5,538
  • 2
  • 10
  • 34
0

Providing a solution with imperative programming by a simple for loop.

This performs better when doing the zip operation on huge data sets compared to the convenient array functions like map() and forEach().

Example:

const a = [1, 2, 3];
const b = ['a', 'b', 'c'];
const result = [];
for (let i = 0; i < a.length; i++) {
  result.push([a[i], b[i]]);
}
console.log(result);

And if you want a 1 line simpler solution then you can use a library like ramda which has a zip function.

Example:

const a = [1, 2, 3];
const b = ['a', 'b', 'c'];
const result = R.zip(a, b);
console.log(result);
varad_s
  • 764
  • 1
  • 12
  • 24