53

I want to split an array into pairs of arrays.

var arr = [2, 3, 4, 5, 6, 4, 3, 5, 5]

would be

var newarr = [
    [2, 3],
    [4, 5],
    [6, 4],
    [3, 5],
    [5]
]
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Tormod Smith
  • 881
  • 1
  • 7
  • 18

16 Answers16

72

You can use js reduce

initialArray.reduce(function(result, value, index, array) {
  if (index % 2 === 0)
    result.push(array.slice(index, index + 2));
  return result;
}, []);
Vbyec
  • 883
  • 7
  • 10
  • 1
    doesn't get shorter than that, best approach here! (and no extra libs) – chrismarx Aug 03 '17 at 16:06
  • Make sure to notice that initialValue (the "[]"), that's easy to forget - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce – chrismarx Aug 03 '17 at 16:07
  • 3
    or you can go with a spread operator, almost a one liner :D ```array.reduce((result, value, index, sourceArray) => index % 2 === 0 ? [...result, sourceArray.slice(index, index + 2)] : result, [])``` – mkbctrl Mar 21 '21 at 08:20
19

Lodash has a method for this: https://lodash.com/docs/4.17.10#chunk

_.chunk([2,3,4,5,6,4,3,5,5], 2); // => [[2,3],[4,5],[6,4],[3,5],[5]]

vinniecent
  • 322
  • 2
  • 5
15

There's no pre-baked function to do that, but here's a simple solution:

var splitPairs = function(arr) {
    var pairs = [];
    for (var i=0 ; i<arr.length ; i+=2) {
        if (arr[i+1] !== undefined) {
            pairs.push ([arr[i], arr[i+1]]);
        } else {
            pairs.push ([arr[i]]);
        }
    }
    return pairs;
};
ChadF
  • 1,750
  • 10
  • 22
  • What about the odd length for `arr` that the OP shows? – jfriend00 Jul 11 '15 at 00:50
  • Good catch, I modified my code for that case. Thank you – ChadF Jul 11 '15 at 01:13
  • Looks like you're missing a closing bracket in your first push statement – vol7ron Jul 11 '15 at 01:34
  • Just in case someone ends up here for this: I was running into infinite loops because I did `for(var i = 0; i < arr.length; i + 2)`. The index was not updating because `i + 2` doesn't set the `i` value. `i += 2` works because it sets the `i` value. This is why going functional with map/reduce is way better whenever possible! – larrydalmeida Jan 22 '18 at 04:16
13

Yet another that's a bit of a mish-mash of the already-posted answers. Adding it because having read the answers I still felt things could be a little easier to read:

var groups = [];

for(var i = 0; i < arr.length; i += 2)
{
    groups.push(arr.slice(i, i + 2));
}
Matt Lacey
  • 8,227
  • 35
  • 58
12

There is now the flexible Array#flatMap(value, index, array):

const pairs = arr.flatMap((_, i, a) => i % 2 ? [] : [a.slice(i, i + 2)]);

And the possibly more efficient, but goofy looking Array.from(source, mapfn?):

const pairs = Array.from({ length: arr.length / 2 }, (_, i) => arr.slice(i * 2, i * 2 + 2))
Simon Buchan
  • 12,707
  • 2
  • 48
  • 55
  • The `flatMap()` isn't just a flexible way but the best practice for today standards and should be considered the best answer. – ed1nh0 May 09 '23 at 14:54
4

It's possible to group an array into pairs/chunks in one line without libraries:

function chunks(arr, size = 2) {
  return arr.map((x, i) => i % size == 0 && arr.slice(i, i + size)).filter(x => x)
}
console.log(chunks([1, 2, 3, 4, 5, 6, 7])) // -> [[1, 2], [3, 4], [5, 6], [7]]
doeke
  • 462
  • 5
  • 12
3

Here's a good generic solution:

function splitInto(array, size, inplace) {
    var output, i, group;

    if (inplace) {
        output = array;

        for (i = 0; i < array.length; i++) {
            group = array.splice(i, size);

            output.splice(i, 0, group);
        }
    } else {
        output = [];

        for (i = 0; i < array.length; i += size) {
            output.push(array.slice(i, size + i));
        }
    }

    return output;
}

For your case, you can call it like this:

var arr= [2,3,4,5,6,4,3,5,5];
var newarr = splitInto(arr, 2);

The inplace argument determines whether the operation is done in-place or not.

Here's a demo below:

function splitInto(array, size, inplace) {
    var output, i, group;

    if (inplace) {
        output = array;

        for (i = 0; i < array.length; i++) {
            group = array.splice(i, size);

            output.splice(i, 0, group);
        }
    } else {
        output = [];

        for (i = 0; i < array.length; i += size) {
            output.push(array.slice(i, size + i));
        }
    }

    return output;
}

var arr= [2,3,4,5,6,4,3,5,5];
var newarr = splitInto(arr, 2);

disp(newarr);

// or we can do it in-place...
splitInto(arr, 3, true);

disp(arr);

function disp(array) {  
  var json = JSON.stringify(array);

  var text = document.createTextNode(json);
  var pre = document.createElement('pre');

  pre.appendChild(text);
  document.body.appendChild(pre);
}
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
3

A slightly different approach than using a for loop for comparison. To avoid modifying the original array slice makes a shallow copy since JS passes objects by reference.

function pairArray(a) {
  var temp = a.slice();
  var arr = [];

  while (temp.length) {
    arr.push(temp.splice(0,2));
  }

  return arr;
}

var array = [2,3,4,5,6,4,3,5,5];
var newArr = pairArray(array);

function pairArray(a) {
  var temp = a.slice();
  var arr = [];

  while (temp.length) {
    arr.push(temp.splice(0,2));
  }

  return arr;
}

document.write('<pre>' + JSON.stringify(newArr) + '</pre>');
Jason Cust
  • 10,743
  • 2
  • 33
  • 45
3

I would use lodash for situations like this.

Here is a solution using _.reduce:

var newArr = _(arr).reduce(function(result, value, index) {
  if (index % 2 === 0)
    result.push(arr.slice(index, index + 2));

  return result;
}, []);

var arr = [2,3,4,5,6,4,3,5,5];

var newArr = _(arr).reduce(function(result, value, index) {
  if (index % 2 === 0)
    result.push(arr.slice(index, index + 2));
  
  return result;
}, []);

document.write(JSON.stringify(newArr)); // [[2,3],[4,5],[6,4],[3,5],[5]]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
Mark T
  • 785
  • 6
  • 13
  • Thanks , do the lodash functions require a library to be downloaded as well ? – Tormod Smith Jul 11 '15 at 12:08
  • Yes, lodash is a separate library. You can use the same CDN I used for the snippet. – Mark T Jul 11 '15 at 15:00
  • Lodash isn't needed for this anymore, just for current viewers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce – Brandon Dec 08 '20 at 05:15
  • And when using lodash, why use its `reduce()` function and not directly `chunk()` (see https://stackoverflow.com/a/51264923/1883995)? – YetAnotherFrank Feb 13 '23 at 10:42
2

Here's another solution using lodash helpers:

function toPairs(array) {
  const evens = array.filter((o, i) => i % 2);
  const odds = array.filter((o, i) => !(i % 2));
  return _.zipWith(evens, odds, (e, o) => e ? [o, e] : [o]);
}
console.log(toPairs([2,3,4,5,6,4,3,5,5]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
alden
  • 439
  • 3
  • 8
  • You can find a similar example with Immutable.js there: https://facebook.github.io/immutable-js/docs/#/List/zip – vcarel May 24 '17 at 15:42
1

const items = [1, 2, 3, 4, 5];

const createBucket = (bucketItems, bucketSize) => buckets => {
  return bucketItems.length === 0 ? buckets : [...buckets, bucketItems.splice(0, bucketSize)];
};

const bucketWithItems = items.reduce(createBucket([...items], 4), []);
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
chirag
  • 91
  • 1
  • 1
0

Here is a short and more generic solution:

function splitArrayIntoPairs(arr, n) {
 var len = arr.length
  var pairs = []

  for (let i = 0; i < len; i += n) {
    var temp = []
    for (var j = i; j < (i + n); j++) {
      if (arr[j] !== undefined) {
        temp.push(arr[j])
      }
    }
    pairs.push(temp)
  }
  return pairs
}

Where arr is your array and n is no of pairs

madhurgarg
  • 1,301
  • 2
  • 11
  • 17
0

This combines some of the answers above but without Object.fromEntires. The output is similar to what you would get with minimist.

    const splitParameters = (args) => {
      const split = (arg) => (arg.includes("=") ? arg.split("=") : [arg]);
    
      return args.reduce((params, arg) => [...params, ...split(arg)], []);
    };
    
    const createPairs = (args) =>
      Array.from({ length: args.length / 2 }, (_, i) =>
        args.slice(i * 2, i * 2 + 2)
      );
    
    const createParameters = (pairs) =>
      pairs.reduce(
        (flags, value) => ({
          ...flags,
          ...{ [value[0].replace("--", "")]: value[1] }
        }),
        {}
      );
    
    const getCliParameters = (args) => {
      const pairs = createPairs(splitParameters(args));
      const paramaters = createParameters(pairs);
    
      console.log(paramaters);
    
      return paramaters;
    };
 

    //const argsFromNodeCli = process.argv.slice(2); // For node
      
    const testArgs = [
      "--url",
      "https://www.google.com",
      "--phrases=hello,hi,bye,ok"
    ];
    
    const output = getCliParameters(testArgs);
    document.body.innerText = JSON.stringify(output);
techmsi
  • 433
  • 1
  • 5
  • 21
0

Here is another concise but still efficient solution using modern JavaScript (arrow function, Array.prototype.at):

splitPairs = arr =>
    arr.reduce((pairs, n, i) =>
        (i % 2 ? pairs.at(-1).push(n)
               : pairs.push([n]),
        pairs), []);

It is (memory-)efficient because it just creates one array for the result and one array for each pair and then modifies them. The case where there is an odd number of elements is handled naturally.

When minified, it is also really concise code:

splitPairs = a=>a.reduce((p,n,i)=>(i%2?p.at(-1)[1]=n:p.push([n]),p),[]);
YetAnotherFrank
  • 386
  • 1
  • 8
0

Using ES6 features:

const arr = [2, 3, 4, 5, 6, 4, 3, 5, 5]

const result = arr.slice(arr.length/2).map((_,i)=>arr.slice(i*=2,i+2))

console.log(result)
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27
-1

Here is another generic solution that uses a generator function.

/**
 * Returns a `Generator` of all unique pairs of elements from the given `iterable`.
 * @param iterable The collection of which to find all unique element pairs.
 */
function* pairs(iterable) {
    const seenItems = new Set();
    for (const currentItem of iterable) {
        if (!seenItems.has(currentItem)) {
            for (const seenItem of seenItems) {
                yield [seenItem, currentItem];
            }
            seenItems.add(currentItem);
        }
    }
}

const numbers = [1, 2, 3, 2];
const pairsOfNumbers = pairs(numbers);

console.log(Array.from(pairsOfNumbers));
// [[1,2],[1,3],[2,3]]

What I like about this approach is that it will not consume the next item from the input until it actually needs it. This is especially handy if you feed it a generator as input, since it will respect its lazy execution.

thijsfranck
  • 778
  • 1
  • 10
  • 24
  • Nice, but you're solving another (more complex) problem. The OP asked for the array to be grouped in pairs, not all combination of its distinct elements. The expected result for your example is simply `[[1, 2], [3, 2]]`. – YetAnotherFrank Feb 10 '23 at 15:18