45

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
  • 11,885
  • 5
  • 66
  • 81
Tormod Smith
  • 769
  • 1
  • 6
  • 17

14 Answers14

65

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
  • 813
  • 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
  • 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
  • 324
  • 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,722
  • 9
  • 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
12

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,147
  • 32
  • 57
4

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
  • 727
  • 6
  • 12
  • 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
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
  • 408
  • 5
  • 12
4

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,047
  • 2
  • 46
  • 51
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,263
  • 2
  • 31
  • 42
2

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
  • 44,815
  • 8
  • 87
  • 134
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
  • 429
  • 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
  • 11,885
  • 5
  • 66
  • 81
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,141
  • 1
  • 10
  • 17
0

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
  • 650
  • 1
  • 8
  • 22
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