84

I'm having trouble coming up with code to generate combinations from n number of arrays with m number of elements in them, in JavaScript. I've seen similar questions about this for other languages, but the answers incorporate syntactic or library magic that I'm unsure how to translate.

Consider this data:

[[0,1], [0,1,2,3], [0,1,2]]

3 arrays, with a different number of elements in them. What I want to do is get all combinations by combining an item from each array.

For example:

0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2

And so on.

If the number of arrays were fixed, it would be easy to make a hard coded implementation. But the number of arrays may vary:

[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]

Any help would be much appreciated.

quano
  • 18,812
  • 25
  • 97
  • 108
  • 3
    See also possible duplicates: [Finding All Combinations of JavaScript array values](http://stackoverflow.com/q/4331092/1048572), [cartesian product of multiple arrays in javascript](http://stackoverflow.com/q/12303989/1048572), [JavaScript Golf - Cartesian Product](http://stackoverflow.com/q/4796678/1048572) or [similar](http://stackoverflow.com/questions/linked/4796678?lq=1) – Bergi Oct 01 '13 at 23:17
  • Easiest way to find the Combinations https://stackoverflow.com/a/52098701/8024633 – kathir Aug 30 '18 at 13:51
  • Seems reasonable to link [Cartesian product of multiple arrays in JavaScript](/q/12303989/4642212) as a duplicate target. The only difference seems to be `cartesian([ [ 1, 2 ], [ "a", "b" ] ])` vs. `cartesian([ 1, 2 ], [ "a", "b" ])`, but the function signature trivially needs to be `cartesian(arrays)` vs. `cartesian(...arrays)`, respectively. – Sebastian Simon Sep 23 '21 at 20:21

10 Answers10

168

Here is a quite simple and short one using a recursive helper function:

function cartesian(...args) {
    var r = [], max = args.length-1;
    function helper(arr, i) {
        for (var j=0, l=args[i].length; j<l; j++) {
            var a = arr.slice(0); // clone arr
            a.push(args[i][j]);
            if (i==max)
                r.push(a);
            else
                helper(a, i+1);
        }
    }
    helper([], 0);
    return r;
}

Usage:

cartesian([0,1], [0,1,2,3], [0,1,2]);

To make the function take an array of arrays, just change the signature to function cartesian(args) instead of using rest parameter syntax.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Awesome, thanks. Benchmark is available here: http://jsfiddle.net/9uvfP/ . Your solution takes 0.14 seconds to run 100,000 times, making it the fastest implementation submitted yet. :) – quano Mar 09 '13 at 12:00
  • Ah, I noticed an error in the benchmark. Updated here: http://jsfiddle.net/2xt5F/ . It takes about 0.6 seconds. – quano Mar 09 '13 at 12:13
  • This is similar to the approach I originally took, but couldn't get there... A bit sleep deprived from a new baby, but glad someone did it so I could see!! – Tom Pietrosanti Mar 09 '13 at 14:00
  • Looks like, I'm going to become your fan. You're genious. – BlitZ Mar 26 '14 at 14:49
  • Although the fiddles benchmark @Neob91's answer as the fastest for me, this jsperf seems to suggest that this answer is the fastest: http://jsperf.com/array-combos – maxedison Jun 15 '15 at 14:59
  • There is a use case - `cartesian([], [0,1,2,3], [0,1,2]);` I need [[0,0],[0,1]...] – Durgesh Pal Jul 08 '20 at 07:02
  • @DurgeshPal But that's not a true cartesian product then. Multiplying by 0 will always yield zero. You want `cartesian(...[[], [0,1,2,3], [0,1,2]].filter(arr => arr.length > 0))` – Bergi Jul 08 '20 at 11:02
20

I suggest a simple recursive generator function:

// JS
function* cartesianIterator(head, ...tail) {
  const remainder = tail.length ? cartesianIterator(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// get values:
const cartesian = items => [...cartesianIterator(items)];
console.log(cartesian(input));
// TS
function* cartesianIterator<T>(items: T[][]): Generator<T[]> {
  const remainder = items.length > 1 ? cartesianIterator(items.slice(1)) : [[]];
  for (let r of remainder) for (let h of items.at(0)!) yield [h, ...r];
}

// get values:
const cartesian = <T>(items: T[][]) => [...cartesianIterator(items)];
console.log(cartesian(input));

Pe Wu
  • 533
  • 4
  • 12
le_m
  • 19,302
  • 9
  • 64
  • 74
19

You could take an iterative approach by building sub arrays.

var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
    result = parts.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));

console.log(result.map(a => a.join(', ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 2
    How about when parts are `[[0, 1], [0, 1, 2, 3], [[0], [1], [2]]]`..? – Redu Mar 30 '18 at 21:00
  • While there seems to be a competition going on to cram multiple lines into one line (minifying), this code is undoubtedly quite elegant. – DarkNeuron Aug 29 '18 at 11:39
7

const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
console.log(charSet.reduce((a,b)=>a.flatMap(x=>b.map(y=>x+y)),['']))
井上智文
  • 1,905
  • 17
  • 14
6

After doing a little research I discovered a previous related question: Finding All Combinations of JavaScript array values

I've adapted some of the code from there so that it returns an array of arrays containing all of the permutations:

function(arraysToCombine) {
    var divisors = [];
    for (var i = arraysToCombine.length - 1; i >= 0; i--) {
       divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
    }

    function getPermutation(n, arraysToCombine) {
       var result = [], 
           curArray;    
       for (var i = 0; i < arraysToCombine.length; i++) {
          curArray = arraysToCombine[i];
          result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
       }    
       return result;
    }

    var numPerms = arraysToCombine[0].length;
    for(var i = 1; i < arraysToCombine.length; i++) {
        numPerms *= arraysToCombine[i].length;
    }

    var combinations = [];
    for(var i = 0; i < numPerms; i++) {
        combinations.push(getPermutation(i, arraysToCombine));
    }
    return combinations;
}

I've put a working copy at http://jsfiddle.net/7EakX/ that takes the array you gave earlier ([[0,1], [0,1,2,3], [0,1,2]]) and outputs the result to the browser console.

Community
  • 1
  • 1
Neil Mountford
  • 1,951
  • 3
  • 21
  • 31
  • Works great. I made a benchmark: http://jsfiddle.net/kLfq9/ . Your solution takes about 0.5 seconds to run 100,000 times in Chrome on my computer. – quano Mar 09 '13 at 11:44
3

Just for fun, here's a more functional variant of the solution in my first answer:

function cartesian() {
    var r = [], args = Array.from(arguments);
    args.reduceRight(function(cont, factor, i) {
        return function(arr) {
            for (var j=0, l=factor.length; j<l; j++) {
                var a = arr.slice(); // clone arr
                a[i] = factor[j];
                cont(a);
            }
        };
    }, Array.prototype.push.bind(r))(new Array(args.length));
    return r;
}

Alternative, for full speed we can dynamically compile our own loops:

function cartesian() {
    return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
    var args = [],
        indent = "",
        up = "",
        down = "";
    for (var i=0; i<n; i++) {
        var arr = "$"+String.fromCharCode(97+i),
            ind = String.fromCharCode(105+i);
        args.push(arr);
        up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
        down = indent+"}\n"+down;
        indent += "  ";
        up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
    }
    var body = "var res=[],\n    arr=[];\n"+up+indent+"res.push(arr.slice());\n"+down+"return res;";
    return cartesian.cache[n] = new Function(args, body);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2
var f = function(arr){
    if(typeof arr !== 'object'){
        return false;
    }

    arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
    var len = arr.length;

    var nextPerm = function(){ // increase the counter(s)
        var i = 0;

        while(i < len)
        {
            arr[i].counter++;

            if(arr[i].counter >= arr[i].length){
                arr[i].counter = 0;
                i++;
            }else{
                return false;
            }
        }

        return true;
    };

    var getPerm = function(){ // get the current permutation
        var perm_arr = [];

        for(var i = 0; i < len; i++)
        {
            perm_arr.push(arr[i][arr[i].counter]);
        }

        return perm_arr;
    };

    var new_arr = [];

    for(var i = 0; i < len; i++) // set up a counter property inside the arrays
    {
        arr[i].counter = 0;
    }

    while(true)
    {
        new_arr.push(getPerm()); // add current permutation to the new array

        if(nextPerm() === true){ // get next permutation, if returns true, we got them all
            break;
        }
    }

    return new_arr;
};
Neob91
  • 344
  • 2
  • 12
  • Thanks. Benchmark available here: http://jsfiddle.net/6cxEH/ . Your solution takes around 0.6 seconds to run 100,000 times. – quano Mar 09 '13 at 12:02
2

Here's another way of doing it. I treat the indices of all of the arrays like a number whose digits are all different bases (like time and dates), using the length of the array as the radix.

So, using your first set of data, the first digit is base 2, the second is base 4, and the third is base 3. The counter starts 000, then goes 001, 002, then 010. The digits correspond to indices in the arrays, and since order is preserved, this is no problem.

I have a fiddle with it working here: http://jsfiddle.net/Rykus0/DS9Ea/1/

and here is the code:

// Arbitrary base x number class 
var BaseX = function(initRadix){
    this.radix     = initRadix ? initRadix : 1;    
    this.value     = 0;
    this.increment = function(){
        return( (this.value = (this.value + 1) % this.radix) === 0);
    }
}

function combinations(input){
    var output    = [],    // Array containing the resulting combinations
        counters  = [],    // Array of counters corresponding to our input arrays
        remainder = false, // Did adding one cause the previous digit to rollover?
        temp;              // Holds one combination to be pushed into the output array

    // Initialize the counters
    for( var i = input.length-1; i >= 0; i-- ){
        counters.unshift(new BaseX(input[i].length));
    }

    // Get all possible combinations
    // Loop through until the first counter rolls over
    while( !remainder ){
        temp      = [];   // Reset the temporary value collection array
        remainder = true; // Always increment the last array counter

        // Process each of the arrays
        for( i = input.length-1; i >= 0; i-- ){
            temp.unshift(input[i][counters[i].value]); // Add this array's value to the result

            // If the counter to the right rolled over, increment this one.
            if( remainder ){
                remainder = counters[i].increment();
            }
        }
        output.push(temp); // Collect the results.
    }

    return output;
}

// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
Tom Pietrosanti
  • 4,184
  • 2
  • 24
  • 29
  • 1
    Thank you for the solution. Benchmark is available here: http://jsfiddle.net/XgyPC/ . It runs your function 100,000 times. It takes about 1 second on my computer in Chrome. – quano Mar 09 '13 at 11:45
  • Excellent! Thank you for running the benchmark. I was wondering how it would perform, and hadn't put much thought into that aspect. This is a fun little problem to solve, so I might give it another go. – Tom Pietrosanti Mar 09 '13 at 13:57
2

You can use a recursive function to get all combinations

const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];

let loopOver = (arr, str = '', final = []) => {
  if (arr.length > 1) {
    arr[0].forEach(v => loopOver(arr.slice(1), str + v, final))
  } else {
    arr[0].forEach(v => final.push(str + v))
  }
  return final
}

console.log(loopOver(charSet))

This code can still be shorten using ternary but i prefer the first version for readability

const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];

let loopOver = (arr, str = '') => arr[0].map(v => arr.length > 1 ? loopOver(arr.slice(1), str + v) : str + v).flat()

console.log(loopOver(charSet))
Code Maniac
  • 37,143
  • 5
  • 39
  • 60
1

Another implementation with ES6 recursive style

Array.prototype.cartesian = function(a,...as){
  return a ? this.reduce((p,c) => (p.push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
           : this;
};

console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));
Redu
  • 25,060
  • 6
  • 56
  • 76