Related
I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]
The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));
It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.
In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]
A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));
How would you implement the Cartesian product of multiple arrays in JavaScript?
As an example,
cartesian([1, 2], [10, 20], [100, 200, 300])
should return
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
2020 Update: 1-line (!) answer with vanilla JS
Original 2017 Answer: 2-line answer with vanilla JS:
(see updates below)
All of the answers here are overly complicated, most of them take 20 lines of code or even more.
This example uses just two lines of vanilla JavaScript, no lodash, underscore or other libraries:
let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;
Update:
This is the same as above but improved to strictly follow the Airbnb JavaScript Style Guide - validated using ESLint with eslint-config-airbnb-base:
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
Special thanks to ZuBB for letting me know about linter problems with the original code.
Update 2020:
Since I wrote this answer we got even better builtins, that can finally let us reduce (no pun intended) the code to just 1 line!
const cartesian =
(...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Special thanks to inker for suggesting the use of reduce.
Special thanks to Bergi for suggesting the use of the newly added flatMap.
Special thanks to ECMAScript 2019 for adding flat and flatMap to the language!
Example
This is the exact example from your question:
let output = cartesian([1,2],[10,20],[100,200,300]);
Output
This is the output of that command:
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ]
Demo
See demos on:
JS Bin with Babel (for old browsers)
JS Bin without Babel (for modern browsers)
Syntax
The syntax that I used here is nothing new.
My example uses the spread operator and the rest parameters - features of JavaScript defined in the 6th edition of the ECMA-262 standard published on June 2015 and developed much earlier, better known as ES6 or ES2015. See:
http://www.ecma-international.org/ecma-262/6.0/
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
The new methods from the Update 2020 example was added in ES2019:
http://www.ecma-international.org/ecma-262/10.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
It makes code like this so simple that it's a sin not to use it. For old platforms that don't support it natively you can always use Babel or other tools to transpile it to older syntax - and in fact my example transpiled by Babel is still shorter and simpler than most of the examples here, but it doesn't really matter because the output of transpilation is not something that you need to understand or maintain, it's just a fact that I found interesting.
Conclusion
There's no need to write hundred of lines of code that is hard to maintain and there is no need to use entire libraries for such a simple thing, when two lines of vanilla JavaScript can easily get the job done. As you can see it really pays off to use modern features of the language and in cases where you need to support archaic platforms with no native support of the modern features you can always use Babel, TypeScript or other tools to transpile the new syntax to the old one.
Don't code like it's 1995
JavaScript evolves and it does so for a reason. TC39 does an amazing job of the language design with adding new features and the browser vendors do an amazing job of implementing those features.
To see the current state of native support of any given feature in the browsers, see:
http://caniuse.com/
https://kangax.github.io/compat-table/
To see the support in Node versions, see:
http://node.green/
To use modern syntax on platforms that don't support it natively, use Babel or TypeScript:
https://babeljs.io/
https://www.typescriptlang.org/
Here is a functional solution to the problem (without any mutable variable!) using reduce and flatten, provided by underscore.js:
function cartesianProductOf() {
return _.reduce(arguments, function(a, b) {
return _.flatten(_.map(a, function(x) {
return _.map(b, function(y) {
return x.concat([y]);
});
}), true);
}, [ [] ]);
}
// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>
Remark: This solution was inspired by http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
Here's a modified version of #viebel's code in plain Javascript, without using any library:
function cartesianProduct(arr) {
return arr.reduce(function(a,b){
return a.map(function(x){
return b.map(function(y){
return x.concat([y]);
})
}).reduce(function(a,b){ return a.concat(b) },[])
}, [[]])
}
var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));
The following efficient generator function returns the cartesian product of all given iterables:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));
It accepts arrays, strings, sets and all other objects implementing the iterable protocol.
Following the specification of the n-ary cartesian product it yields
[] if one or more given iterables are empty, e.g. [] or ''
[[a]] if a single iterable containing a single value a is given.
All other cases are handled as expected as demonstrated by the following test cases:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Test cases:
console.log([...cartesian([])]); // []
console.log([...cartesian([1])]); // [[1]]
console.log([...cartesian([1, 2])]); // [[1], [2]]
console.log([...cartesian([1], [])]); // []
console.log([...cartesian([1, 2], [])]); // []
console.log([...cartesian([1], [2])]); // [[1, 2]]
console.log([...cartesian([1], [2], [3])]); // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]); // [[1, 3], [2, 3], [1, 4], [2, 4]]
console.log([...cartesian('')]); // []
console.log([...cartesian('ab', 'c')]); // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]); // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]
console.log([...cartesian(new Set())]); // []
console.log([...cartesian(new Set([1]))]); // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]
It seems the community thinks this to be trivial and/or easy to find a reference implementation. However, upon brief inspection I couldn't find one, … either that or maybe it's just that I like re-inventing the wheel or solving classroom-like programming problems. Either way its your lucky day:
function cartProd(paramArray) {
function addTo(curr, args) {
var i, copy,
rest = args.slice(1),
last = !rest.length,
result = [];
for (i = 0; i < args[0].length; i++) {
copy = curr.slice();
copy.push(args[0][i]);
if (last) {
result.push(copy);
} else {
result = result.concat(addTo(copy, rest));
}
}
return result;
}
return addTo([], Array.prototype.slice.call(arguments));
}
>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
[1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100],
[1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200],
[2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
]
Full reference implementation that's relatively efficient… 😁
On efficiency: You could gain some by taking the if out of the loop and having 2 separate loops since it is technically constant and you'd be helping with branch prediction and all that mess, but that point is kind of moot in JavaScript.
Here's a non-fancy, straightforward recursive solution:
function cartesianProduct(a) { // a = array of array
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0]; // the first array of a
a = cartesianProduct(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length)
for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
console.log(cartesianProduct([[1, 2], [10, 20], [100, 200, 300]]));
// [
// [1,10,100],[1,10,200],[1,10,300],
// [1,20,100],[1,20,200],[1,20,300],
// [2,10,100],[2,10,200],[2,10,300],
// [2,20,100],[2,20,200],[2,20,300]
// ]
Here is a one-liner using the native ES2019 flatMap. No libraries needed, just a modern browser (or transpiler):
data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);
It's essentially a modern version of viebel's answer, without lodash.
Here is a recursive way that uses an ECMAScript 2015 generator function so you don't have to create all of the tuples at once:
function* cartesian() {
let arrays = arguments;
function* doCartesian(i, prod) {
if (i == arrays.length) {
yield prod;
} else {
for (let j = 0; j < arrays[i].length; j++) {
yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
}
}
}
yield* doCartesian(0, []);
}
console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));
This is a pure ES6 solution using arrow functions
function cartesianProduct(arr) {
return arr.reduce((a, b) =>
a.map(x => b.map(y => x.concat(y)))
.reduce((a, b) => a.concat(b), []), [[]]);
}
var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));
functional programming
This question is tagged functional-programming so let's take a look at the List monad:
One application for this monadic list is representing nondeterministic computation. List can hold results for all execution paths in an algorithm...
Well that sounds like a perfect fit for cartesian. JavaScript gives us Array and the monadic binding function is Array.prototype.flatMap, so let's put them to use -
const cartesian = (...all) => {
const loop = (t, a, ...more) =>
a === undefined
? [ t ]
: a.flatMap(x => loop([ ...t, x ], ...more))
return loop([], ...all)
}
console.log(cartesian([1,2], [10,20], [100,200,300]))
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
more recursion
Other recursive implementations include -
const cartesian = (a, ...more) =>
a == null
? [[]]
: cartesian(...more).flatMap(c => a.map(v => [v,...c]))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[2,10,100]
[1,20,100]
[2,20,100]
[1,10,200]
[2,10,200]
[1,20,200]
[2,20,200]
[1,10,300]
[2,10,300]
[1,20,300]
[2,20,300]
Note the different order above. You can get lexicographic order by inverting the two loops. Be careful not avoid duplicating work by calling cartesian inside the loop like Nick's answer -
const bind = (x, f) => f(x)
const cartesian = (a, ...more) =>
a == null
? [[]]
: bind(cartesian(...more), r => a.flatMap(v => r.map(c => [v,...c])))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
generators
Another option is to use generators. A generator is a good fit for combinatorics because the solution space can become very large. Generators offer lazy evaluation so they can be paused/resumed/canceled at any time -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const v of a)
for (const c of cartesian(...more)) // ⚠️
yield [v, ...c]
}
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
Maybe you saw that we called cartesian in a loop in the generator. If you suspect that can be optimized, it can! Here we use a generic tee function that forks any iterator n times -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const t of tee(cartesian(...more), a.length)) // ✅
for (const v of a)
for (const c of t) // ✅
yield [v, ...c]
}
Where tee is implemented as -
function tee(g, n = 2) {
const memo = []
function* iter(i) {
while (true) {
if (i >= memo.length) {
const w = g.next()
if (w.done) return
memo.push(w.value)
}
else yield memo[i++]
}
}
return Array.from(Array(n), _ => iter(0))
}
Even in small tests cartesian generator implemented with tee performs twice as fast.
Using a typical backtracking with ES6 generators,
function cartesianProduct(...arrays) {
let current = new Array(arrays.length);
return (function* backtracking(index) {
if(index == arrays.length) yield current.slice();
else for(let num of arrays[index]) {
current[index] = num;
yield* backtracking(index+1);
}
})(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }
Below there is a similar version compatible with older browsers.
function cartesianProduct(arrays) {
var result = [],
current = new Array(arrays.length);
(function backtracking(index) {
if(index == arrays.length) return result.push(current.slice());
for(var i=0; i<arrays[index].length; ++i) {
current[index] = arrays[index][i];
backtracking(index+1);
}
})(0);
return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
console.log('[' + item.join(', ') + ']');
});
div.as-console-wrapper { max-height: 100%; }
A single line approach, for better reading with indentations.
result = data.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
It takes a single array with arrays of wanted cartesian items.
var data = [[1, 2], [10, 20], [100, 200, 300]],
result = data.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; }
A coffeescript version with lodash:
_ = require("lodash")
cartesianProduct = ->
return _.reduceRight(arguments, (a,b) ->
_.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
, [ [] ])
For those who needs TypeScript (reimplemented #Danny's answer)
/**
* Calculates "Cartesian Product" sets.
* #example
* cartesianProduct([[1,2], [4,8], [16,32]])
* Returns:
* [
* [1, 4, 16],
* [1, 4, 32],
* [1, 8, 16],
* [1, 8, 32],
* [2, 4, 16],
* [2, 4, 32],
* [2, 8, 16],
* [2, 8, 32]
* ]
* #see https://stackoverflow.com/a/36234242/1955709
* #see https://en.wikipedia.org/wiki/Cartesian_product
* #param arr {T[][]}
* #returns {T[][]}
*/
function cartesianProduct<T> (arr: T[][]): T[][] {
return arr.reduce((a, b) => {
return a.map(x => {
return b.map(y => {
return x.concat(y)
})
}).reduce((c, d) => c.concat(d), [])
}, [[]] as T[][])
}
In my particular setting, the "old-fashioned" approach seemed to be more efficient than the methods based on more modern features. Below is the code (including a small comparison with other solutions posted in this thread by #rsp and #sebnukem) should it prove useful to someone else as well.
The idea is following. Let's say we are constructing the outer product of N arrays, a_1,...,a_N each of which has m_i components. The outer product of these arrays has M=m_1*m_2*...*m_N elements and we can identify each of them with a N-dimensional vector the components of which are positive integers and i-th component is strictly bounded from above by m_i. For example, the vector (0, 0, ..., 0) would correspond to the particular combination within which one takes the first element from each array, while (m_1-1, m_2-1, ..., m_N-1) is identified with the combination where one takes the last element from each array. Thus in order to construct all M combinations, the function below consecutively constructs all such vectors and for each of them identifies the corresponding combination of the elements of the input arrays.
function cartesianProduct(){
const N = arguments.length;
var arr_lengths = Array(N);
var digits = Array(N);
var num_tot = 1;
for(var i = 0; i < N; ++i){
const len = arguments[i].length;
if(!len){
num_tot = 0;
break;
}
digits[i] = 0;
num_tot *= (arr_lengths[i] = len);
}
var ret = Array(num_tot);
for(var num = 0; num < num_tot; ++num){
var item = Array(N);
for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
ret[num] = item;
for(var idx = 0; idx < N; ++idx){
if(digits[idx] == arr_lengths[idx]-1){
digits[idx] = 0;
}else{
digits[idx] += 1;
break;
}
}
}
return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0];
a = cartesianProduct_sebnukem(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length) for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];
let fns = {
'cartesianProduct': function(args){ return cartesianProduct(...args); },
'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};
Object.keys(fns).forEach(fname => {
console.time(fname);
const ret = fns[fname](args);
console.timeEnd(fname);
});
with node v6.12.2, I get following timings:
cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
You could reduce the 2D array. Use flatMap on the accumulator array to get acc.length x curr.length number of combinations in each loop. [].concat(c, n) is used because c is a number in the first iteration and an array afterwards.
const data = [ [1, 2], [10, 20], [100, 200, 300] ];
const output = data.reduce((acc, curr) =>
acc.flatMap(c => curr.map(n => [].concat(c, n)))
)
console.log(JSON.stringify(output))
(This is based on Nina Scholz's answer)
Here's a recursive one-liner that works using only flatMap and map:
const inp = [
[1, 2],
[10, 20],
[100, 200, 300]
];
const cartesian = (first, ...rest) =>
rest.length ? first.flatMap(v => cartesian(...rest).map(c => [v].concat(c)))
: first;
console.log(cartesian(...inp));
A few of the answers under this topic fail when any of the input arrays contains an array item. You you better check that.
Anyways no need for underscore, lodash whatsoever. I believe this one should do it with pure JS ES6, as functional as it gets.
This piece of code uses a reduce and a nested map, simply to get the cartesian product of two arrays however the second array comes from a recursive call to the same function with one less array; hence.. a[0].cartesian(...a.slice(1))
Array.prototype.cartesian = function(...a){
return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
: this;
};
var arr = ['a', 'b', 'c'],
brr = [1,2,3],
crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr)));
No libraries needed! :)
Needs arrow functions though and probably not that efficient. :/
const flatten = (xs) =>
xs.flat(Infinity)
const binaryCartesianProduct = (xs, ys) =>
xs.map((xi) => ys.map((yi) => [xi, yi])).flat()
const cartesianProduct = (...xss) =>
xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))
Modern JavaScript in just a few lines. No external libraries or dependencies like Lodash.
function cartesian(...arrays) {
return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}
console.log(
cartesian([1, 2], [10, 20], [100, 200, 300])
.map(arr => JSON.stringify(arr))
.join('\n')
);
A more readable implementation
function productOfTwo(one, two) {
return one.flatMap(x => two.map(y => [].concat(x, y)));
}
function product(head = [], ...tail) {
if (tail.length === 0) return head;
return productOfTwo(head, product(...tail));
}
const test = product(
[1, 2, 3],
['a', 'b']
);
console.log(JSON.stringify(test));
Another, even more simplified, 2021-style answer using only reduce, map, and concat methods:
const cartesian = (...arr) => arr.reduce((a,c) => a.map(e => c.map(f => e.concat([f]))).reduce((a,c) => a.concat(c), []), [[]]);
console.log(cartesian([1, 2], [10, 20], [100, 200, 300]));
For those happy with a ramda solution:
import { xprod, flatten } from 'ramda';
const cartessian = (...xs) => xs.reduce(xprod).map(flatten)
Or the same without dependencies and two lego blocks for free (xprod and flatten):
const flatten = xs => xs.flat();
const xprod = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]));
const cartessian = (...xs) => xs.reduce(xprod).map(flatten);
Just for a choice a real simple implementation using array's reduce:
const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");
const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);
A simple "mind and visually friendly" solution.
// t = [i, length]
const moveThreadForwardAt = (t, tCursor) => {
if (tCursor < 0)
return true; // reached end of first array
const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
t[tCursor][0] = newIndex;
if (newIndex == 0)
return moveThreadForwardAt(t, tCursor - 1);
return false;
}
const cartesianMult = (...args) => {
let result = [];
const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
let reachedEndOfFirstArray = false;
while (false == reachedEndOfFirstArray) {
result.push(t.map((v, i) => args[i][v[0]]));
reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
}
return result;
}
// cartesianMult(
// ['a1', 'b1', 'c1'],
// ['a2', 'b2'],
// ['a3', 'b3', 'c3'],
// ['a4', 'b4']
// );
console.log(cartesianMult(
['a1'],
['a2', 'b2'],
['a3', 'b3']
));
Yet another implementation. Not the shortest or fancy, but fast:
function cartesianProduct() {
var arr = [].slice.call(arguments),
intLength = arr.length,
arrHelper = [1],
arrToReturn = [];
for (var i = arr.length - 1; i >= 0; i--) {
arrHelper.unshift(arrHelper[0] * arr[i].length);
}
for (var i = 0, l = arrHelper[0]; i < l; i++) {
arrToReturn.push([]);
for (var j = 0; j < intLength; j++) {
arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
}
}
return arrToReturn;
}
A simple, modified version of #viebel's code in plain Javascript:
function cartesianProduct(...arrays) {
return arrays.reduce((a, b) => {
return [].concat(...a.map(x => {
const next = Array.isArray(x) ? x : [x];
return [].concat(b.map(y => next.concat(...[y])));
}));
});
}
const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);
console.log(product);
/*
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ];
*/
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))
This is for 3 arrays.
Some answers gave a way for any number of arrays.
This can easily contract or expand to less or more arrays.
I needed combinations of one set with repetitions, so I could have used:
f(a,a,a)
but used:
f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
A non-recursive approach that adds the ability to filter and modify the products before actually adding them to the result set.
Note: the use of .map rather than .forEach. In some browsers, .map runs faster.
function crossproduct(arrays, rowtest, rowaction) {
// Calculate the number of elements needed in the result
var result_elems = 1,
row_size = arrays.length;
arrays.map(function(array) {
result_elems *= array.length;
});
var temp = new Array(result_elems),
result = [];
// Go through each array and add the appropriate
// element to each element of the temp
var scale_factor = result_elems;
arrays.map(function(array) {
var set_elems = array.length;
scale_factor /= set_elems;
for (var i = result_elems - 1; i >= 0; i--) {
temp[i] = (temp[i] ? temp[i] : []);
var pos = i / scale_factor % set_elems;
// deal with floating point results for indexes,
// this took a little experimenting
if (pos < 1 || pos % 1 <= .5) {
pos = Math.floor(pos);
} else {
pos = Math.min(array.length - 1, Math.ceil(pos));
}
temp[i].push(array[pos]);
if (temp[i].length === row_size) {
var pass = (rowtest ? rowtest(temp[i]) : true);
if (pass) {
if (rowaction) {
result.push(rowaction(temp[i]));
} else {
result.push(temp[i]);
}
}
}
}
});
return result;
}
console.log(
crossproduct([[1, 2], [10, 20], [100, 200, 300]],null,null)
)
Similar in spirit to others, but highly readable imo.
function productOfTwo(a, b) {
return a.flatMap(c => b.map(d => [c, d].flat()));
}
[['a', 'b', 'c'], ['+', '-'], [1, 2, 3]].reduce(productOfTwo);
How would you implement the Cartesian product of multiple arrays in JavaScript?
As an example,
cartesian([1, 2], [10, 20], [100, 200, 300])
should return
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
2020 Update: 1-line (!) answer with vanilla JS
Original 2017 Answer: 2-line answer with vanilla JS:
(see updates below)
All of the answers here are overly complicated, most of them take 20 lines of code or even more.
This example uses just two lines of vanilla JavaScript, no lodash, underscore or other libraries:
let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;
Update:
This is the same as above but improved to strictly follow the Airbnb JavaScript Style Guide - validated using ESLint with eslint-config-airbnb-base:
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
Special thanks to ZuBB for letting me know about linter problems with the original code.
Update 2020:
Since I wrote this answer we got even better builtins, that can finally let us reduce (no pun intended) the code to just 1 line!
const cartesian =
(...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Special thanks to inker for suggesting the use of reduce.
Special thanks to Bergi for suggesting the use of the newly added flatMap.
Special thanks to ECMAScript 2019 for adding flat and flatMap to the language!
Example
This is the exact example from your question:
let output = cartesian([1,2],[10,20],[100,200,300]);
Output
This is the output of that command:
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ]
Demo
See demos on:
JS Bin with Babel (for old browsers)
JS Bin without Babel (for modern browsers)
Syntax
The syntax that I used here is nothing new.
My example uses the spread operator and the rest parameters - features of JavaScript defined in the 6th edition of the ECMA-262 standard published on June 2015 and developed much earlier, better known as ES6 or ES2015. See:
http://www.ecma-international.org/ecma-262/6.0/
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
The new methods from the Update 2020 example was added in ES2019:
http://www.ecma-international.org/ecma-262/10.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
It makes code like this so simple that it's a sin not to use it. For old platforms that don't support it natively you can always use Babel or other tools to transpile it to older syntax - and in fact my example transpiled by Babel is still shorter and simpler than most of the examples here, but it doesn't really matter because the output of transpilation is not something that you need to understand or maintain, it's just a fact that I found interesting.
Conclusion
There's no need to write hundred of lines of code that is hard to maintain and there is no need to use entire libraries for such a simple thing, when two lines of vanilla JavaScript can easily get the job done. As you can see it really pays off to use modern features of the language and in cases where you need to support archaic platforms with no native support of the modern features you can always use Babel, TypeScript or other tools to transpile the new syntax to the old one.
Don't code like it's 1995
JavaScript evolves and it does so for a reason. TC39 does an amazing job of the language design with adding new features and the browser vendors do an amazing job of implementing those features.
To see the current state of native support of any given feature in the browsers, see:
http://caniuse.com/
https://kangax.github.io/compat-table/
To see the support in Node versions, see:
http://node.green/
To use modern syntax on platforms that don't support it natively, use Babel or TypeScript:
https://babeljs.io/
https://www.typescriptlang.org/
Here is a functional solution to the problem (without any mutable variable!) using reduce and flatten, provided by underscore.js:
function cartesianProductOf() {
return _.reduce(arguments, function(a, b) {
return _.flatten(_.map(a, function(x) {
return _.map(b, function(y) {
return x.concat([y]);
});
}), true);
}, [ [] ]);
}
// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>
Remark: This solution was inspired by http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
Here's a modified version of #viebel's code in plain Javascript, without using any library:
function cartesianProduct(arr) {
return arr.reduce(function(a,b){
return a.map(function(x){
return b.map(function(y){
return x.concat([y]);
})
}).reduce(function(a,b){ return a.concat(b) },[])
}, [[]])
}
var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));
The following efficient generator function returns the cartesian product of all given iterables:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));
It accepts arrays, strings, sets and all other objects implementing the iterable protocol.
Following the specification of the n-ary cartesian product it yields
[] if one or more given iterables are empty, e.g. [] or ''
[[a]] if a single iterable containing a single value a is given.
All other cases are handled as expected as demonstrated by the following test cases:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Test cases:
console.log([...cartesian([])]); // []
console.log([...cartesian([1])]); // [[1]]
console.log([...cartesian([1, 2])]); // [[1], [2]]
console.log([...cartesian([1], [])]); // []
console.log([...cartesian([1, 2], [])]); // []
console.log([...cartesian([1], [2])]); // [[1, 2]]
console.log([...cartesian([1], [2], [3])]); // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]); // [[1, 3], [2, 3], [1, 4], [2, 4]]
console.log([...cartesian('')]); // []
console.log([...cartesian('ab', 'c')]); // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]); // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]
console.log([...cartesian(new Set())]); // []
console.log([...cartesian(new Set([1]))]); // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]
It seems the community thinks this to be trivial and/or easy to find a reference implementation. However, upon brief inspection I couldn't find one, … either that or maybe it's just that I like re-inventing the wheel or solving classroom-like programming problems. Either way its your lucky day:
function cartProd(paramArray) {
function addTo(curr, args) {
var i, copy,
rest = args.slice(1),
last = !rest.length,
result = [];
for (i = 0; i < args[0].length; i++) {
copy = curr.slice();
copy.push(args[0][i]);
if (last) {
result.push(copy);
} else {
result = result.concat(addTo(copy, rest));
}
}
return result;
}
return addTo([], Array.prototype.slice.call(arguments));
}
>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
[1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100],
[1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200],
[2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
]
Full reference implementation that's relatively efficient… 😁
On efficiency: You could gain some by taking the if out of the loop and having 2 separate loops since it is technically constant and you'd be helping with branch prediction and all that mess, but that point is kind of moot in JavaScript.
Here's a non-fancy, straightforward recursive solution:
function cartesianProduct(a) { // a = array of array
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0]; // the first array of a
a = cartesianProduct(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length)
for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
console.log(cartesianProduct([[1, 2], [10, 20], [100, 200, 300]]));
// [
// [1,10,100],[1,10,200],[1,10,300],
// [1,20,100],[1,20,200],[1,20,300],
// [2,10,100],[2,10,200],[2,10,300],
// [2,20,100],[2,20,200],[2,20,300]
// ]
Here is a one-liner using the native ES2019 flatMap. No libraries needed, just a modern browser (or transpiler):
data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);
It's essentially a modern version of viebel's answer, without lodash.
Here is a recursive way that uses an ECMAScript 2015 generator function so you don't have to create all of the tuples at once:
function* cartesian() {
let arrays = arguments;
function* doCartesian(i, prod) {
if (i == arrays.length) {
yield prod;
} else {
for (let j = 0; j < arrays[i].length; j++) {
yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
}
}
}
yield* doCartesian(0, []);
}
console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));
This is a pure ES6 solution using arrow functions
function cartesianProduct(arr) {
return arr.reduce((a, b) =>
a.map(x => b.map(y => x.concat(y)))
.reduce((a, b) => a.concat(b), []), [[]]);
}
var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));
functional programming
This question is tagged functional-programming so let's take a look at the List monad:
One application for this monadic list is representing nondeterministic computation. List can hold results for all execution paths in an algorithm...
Well that sounds like a perfect fit for cartesian. JavaScript gives us Array and the monadic binding function is Array.prototype.flatMap, so let's put them to use -
const cartesian = (...all) => {
const loop = (t, a, ...more) =>
a === undefined
? [ t ]
: a.flatMap(x => loop([ ...t, x ], ...more))
return loop([], ...all)
}
console.log(cartesian([1,2], [10,20], [100,200,300]))
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
more recursion
Other recursive implementations include -
const cartesian = (a, ...more) =>
a == null
? [[]]
: cartesian(...more).flatMap(c => a.map(v => [v,...c]))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[2,10,100]
[1,20,100]
[2,20,100]
[1,10,200]
[2,10,200]
[1,20,200]
[2,20,200]
[1,10,300]
[2,10,300]
[1,20,300]
[2,20,300]
Note the different order above. You can get lexicographic order by inverting the two loops. Be careful not avoid duplicating work by calling cartesian inside the loop like Nick's answer -
const bind = (x, f) => f(x)
const cartesian = (a, ...more) =>
a == null
? [[]]
: bind(cartesian(...more), r => a.flatMap(v => r.map(c => [v,...c])))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
generators
Another option is to use generators. A generator is a good fit for combinatorics because the solution space can become very large. Generators offer lazy evaluation so they can be paused/resumed/canceled at any time -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const v of a)
for (const c of cartesian(...more)) // ⚠️
yield [v, ...c]
}
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
Maybe you saw that we called cartesian in a loop in the generator. If you suspect that can be optimized, it can! Here we use a generic tee function that forks any iterator n times -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const t of tee(cartesian(...more), a.length)) // ✅
for (const v of a)
for (const c of t) // ✅
yield [v, ...c]
}
Where tee is implemented as -
function tee(g, n = 2) {
const memo = []
function* iter(i) {
while (true) {
if (i >= memo.length) {
const w = g.next()
if (w.done) return
memo.push(w.value)
}
else yield memo[i++]
}
}
return Array.from(Array(n), _ => iter(0))
}
Even in small tests cartesian generator implemented with tee performs twice as fast.
Using a typical backtracking with ES6 generators,
function cartesianProduct(...arrays) {
let current = new Array(arrays.length);
return (function* backtracking(index) {
if(index == arrays.length) yield current.slice();
else for(let num of arrays[index]) {
current[index] = num;
yield* backtracking(index+1);
}
})(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }
Below there is a similar version compatible with older browsers.
function cartesianProduct(arrays) {
var result = [],
current = new Array(arrays.length);
(function backtracking(index) {
if(index == arrays.length) return result.push(current.slice());
for(var i=0; i<arrays[index].length; ++i) {
current[index] = arrays[index][i];
backtracking(index+1);
}
})(0);
return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
console.log('[' + item.join(', ') + ']');
});
div.as-console-wrapper { max-height: 100%; }
A single line approach, for better reading with indentations.
result = data.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
It takes a single array with arrays of wanted cartesian items.
var data = [[1, 2], [10, 20], [100, 200, 300]],
result = data.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; }
A coffeescript version with lodash:
_ = require("lodash")
cartesianProduct = ->
return _.reduceRight(arguments, (a,b) ->
_.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
, [ [] ])
For those who needs TypeScript (reimplemented #Danny's answer)
/**
* Calculates "Cartesian Product" sets.
* #example
* cartesianProduct([[1,2], [4,8], [16,32]])
* Returns:
* [
* [1, 4, 16],
* [1, 4, 32],
* [1, 8, 16],
* [1, 8, 32],
* [2, 4, 16],
* [2, 4, 32],
* [2, 8, 16],
* [2, 8, 32]
* ]
* #see https://stackoverflow.com/a/36234242/1955709
* #see https://en.wikipedia.org/wiki/Cartesian_product
* #param arr {T[][]}
* #returns {T[][]}
*/
function cartesianProduct<T> (arr: T[][]): T[][] {
return arr.reduce((a, b) => {
return a.map(x => {
return b.map(y => {
return x.concat(y)
})
}).reduce((c, d) => c.concat(d), [])
}, [[]] as T[][])
}
In my particular setting, the "old-fashioned" approach seemed to be more efficient than the methods based on more modern features. Below is the code (including a small comparison with other solutions posted in this thread by #rsp and #sebnukem) should it prove useful to someone else as well.
The idea is following. Let's say we are constructing the outer product of N arrays, a_1,...,a_N each of which has m_i components. The outer product of these arrays has M=m_1*m_2*...*m_N elements and we can identify each of them with a N-dimensional vector the components of which are positive integers and i-th component is strictly bounded from above by m_i. For example, the vector (0, 0, ..., 0) would correspond to the particular combination within which one takes the first element from each array, while (m_1-1, m_2-1, ..., m_N-1) is identified with the combination where one takes the last element from each array. Thus in order to construct all M combinations, the function below consecutively constructs all such vectors and for each of them identifies the corresponding combination of the elements of the input arrays.
function cartesianProduct(){
const N = arguments.length;
var arr_lengths = Array(N);
var digits = Array(N);
var num_tot = 1;
for(var i = 0; i < N; ++i){
const len = arguments[i].length;
if(!len){
num_tot = 0;
break;
}
digits[i] = 0;
num_tot *= (arr_lengths[i] = len);
}
var ret = Array(num_tot);
for(var num = 0; num < num_tot; ++num){
var item = Array(N);
for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
ret[num] = item;
for(var idx = 0; idx < N; ++idx){
if(digits[idx] == arr_lengths[idx]-1){
digits[idx] = 0;
}else{
digits[idx] += 1;
break;
}
}
}
return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0];
a = cartesianProduct_sebnukem(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length) for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];
let fns = {
'cartesianProduct': function(args){ return cartesianProduct(...args); },
'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};
Object.keys(fns).forEach(fname => {
console.time(fname);
const ret = fns[fname](args);
console.timeEnd(fname);
});
with node v6.12.2, I get following timings:
cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
You could reduce the 2D array. Use flatMap on the accumulator array to get acc.length x curr.length number of combinations in each loop. [].concat(c, n) is used because c is a number in the first iteration and an array afterwards.
const data = [ [1, 2], [10, 20], [100, 200, 300] ];
const output = data.reduce((acc, curr) =>
acc.flatMap(c => curr.map(n => [].concat(c, n)))
)
console.log(JSON.stringify(output))
(This is based on Nina Scholz's answer)
Here's a recursive one-liner that works using only flatMap and map:
const inp = [
[1, 2],
[10, 20],
[100, 200, 300]
];
const cartesian = (first, ...rest) =>
rest.length ? first.flatMap(v => cartesian(...rest).map(c => [v].concat(c)))
: first;
console.log(cartesian(...inp));
A few of the answers under this topic fail when any of the input arrays contains an array item. You you better check that.
Anyways no need for underscore, lodash whatsoever. I believe this one should do it with pure JS ES6, as functional as it gets.
This piece of code uses a reduce and a nested map, simply to get the cartesian product of two arrays however the second array comes from a recursive call to the same function with one less array; hence.. a[0].cartesian(...a.slice(1))
Array.prototype.cartesian = function(...a){
return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
: this;
};
var arr = ['a', 'b', 'c'],
brr = [1,2,3],
crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr)));
No libraries needed! :)
Needs arrow functions though and probably not that efficient. :/
const flatten = (xs) =>
xs.flat(Infinity)
const binaryCartesianProduct = (xs, ys) =>
xs.map((xi) => ys.map((yi) => [xi, yi])).flat()
const cartesianProduct = (...xss) =>
xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))
Modern JavaScript in just a few lines. No external libraries or dependencies like Lodash.
function cartesian(...arrays) {
return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}
console.log(
cartesian([1, 2], [10, 20], [100, 200, 300])
.map(arr => JSON.stringify(arr))
.join('\n')
);
A more readable implementation
function productOfTwo(one, two) {
return one.flatMap(x => two.map(y => [].concat(x, y)));
}
function product(head = [], ...tail) {
if (tail.length === 0) return head;
return productOfTwo(head, product(...tail));
}
const test = product(
[1, 2, 3],
['a', 'b']
);
console.log(JSON.stringify(test));
Another, even more simplified, 2021-style answer using only reduce, map, and concat methods:
const cartesian = (...arr) => arr.reduce((a,c) => a.map(e => c.map(f => e.concat([f]))).reduce((a,c) => a.concat(c), []), [[]]);
console.log(cartesian([1, 2], [10, 20], [100, 200, 300]));
For those happy with a ramda solution:
import { xprod, flatten } from 'ramda';
const cartessian = (...xs) => xs.reduce(xprod).map(flatten)
Or the same without dependencies and two lego blocks for free (xprod and flatten):
const flatten = xs => xs.flat();
const xprod = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]));
const cartessian = (...xs) => xs.reduce(xprod).map(flatten);
Just for a choice a real simple implementation using array's reduce:
const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");
const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);
A simple "mind and visually friendly" solution.
// t = [i, length]
const moveThreadForwardAt = (t, tCursor) => {
if (tCursor < 0)
return true; // reached end of first array
const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
t[tCursor][0] = newIndex;
if (newIndex == 0)
return moveThreadForwardAt(t, tCursor - 1);
return false;
}
const cartesianMult = (...args) => {
let result = [];
const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
let reachedEndOfFirstArray = false;
while (false == reachedEndOfFirstArray) {
result.push(t.map((v, i) => args[i][v[0]]));
reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
}
return result;
}
// cartesianMult(
// ['a1', 'b1', 'c1'],
// ['a2', 'b2'],
// ['a3', 'b3', 'c3'],
// ['a4', 'b4']
// );
console.log(cartesianMult(
['a1'],
['a2', 'b2'],
['a3', 'b3']
));
Yet another implementation. Not the shortest or fancy, but fast:
function cartesianProduct() {
var arr = [].slice.call(arguments),
intLength = arr.length,
arrHelper = [1],
arrToReturn = [];
for (var i = arr.length - 1; i >= 0; i--) {
arrHelper.unshift(arrHelper[0] * arr[i].length);
}
for (var i = 0, l = arrHelper[0]; i < l; i++) {
arrToReturn.push([]);
for (var j = 0; j < intLength; j++) {
arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
}
}
return arrToReturn;
}
A simple, modified version of #viebel's code in plain Javascript:
function cartesianProduct(...arrays) {
return arrays.reduce((a, b) => {
return [].concat(...a.map(x => {
const next = Array.isArray(x) ? x : [x];
return [].concat(b.map(y => next.concat(...[y])));
}));
});
}
const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);
console.log(product);
/*
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ];
*/
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))
This is for 3 arrays.
Some answers gave a way for any number of arrays.
This can easily contract or expand to less or more arrays.
I needed combinations of one set with repetitions, so I could have used:
f(a,a,a)
but used:
f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
A non-recursive approach that adds the ability to filter and modify the products before actually adding them to the result set.
Note: the use of .map rather than .forEach. In some browsers, .map runs faster.
function crossproduct(arrays, rowtest, rowaction) {
// Calculate the number of elements needed in the result
var result_elems = 1,
row_size = arrays.length;
arrays.map(function(array) {
result_elems *= array.length;
});
var temp = new Array(result_elems),
result = [];
// Go through each array and add the appropriate
// element to each element of the temp
var scale_factor = result_elems;
arrays.map(function(array) {
var set_elems = array.length;
scale_factor /= set_elems;
for (var i = result_elems - 1; i >= 0; i--) {
temp[i] = (temp[i] ? temp[i] : []);
var pos = i / scale_factor % set_elems;
// deal with floating point results for indexes,
// this took a little experimenting
if (pos < 1 || pos % 1 <= .5) {
pos = Math.floor(pos);
} else {
pos = Math.min(array.length - 1, Math.ceil(pos));
}
temp[i].push(array[pos]);
if (temp[i].length === row_size) {
var pass = (rowtest ? rowtest(temp[i]) : true);
if (pass) {
if (rowaction) {
result.push(rowaction(temp[i]));
} else {
result.push(temp[i]);
}
}
}
}
});
return result;
}
console.log(
crossproduct([[1, 2], [10, 20], [100, 200, 300]],null,null)
)
Similar in spirit to others, but highly readable imo.
function productOfTwo(a, b) {
return a.flatMap(c => b.map(d => [c, d].flat()));
}
[['a', 'b', 'c'], ['+', '-'], [1, 2, 3]].reduce(productOfTwo);
How would you implement the Cartesian product of multiple arrays in JavaScript?
As an example,
cartesian([1, 2], [10, 20], [100, 200, 300])
should return
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
2020 Update: 1-line (!) answer with vanilla JS
Original 2017 Answer: 2-line answer with vanilla JS:
(see updates below)
All of the answers here are overly complicated, most of them take 20 lines of code or even more.
This example uses just two lines of vanilla JavaScript, no lodash, underscore or other libraries:
let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;
Update:
This is the same as above but improved to strictly follow the Airbnb JavaScript Style Guide - validated using ESLint with eslint-config-airbnb-base:
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
Special thanks to ZuBB for letting me know about linter problems with the original code.
Update 2020:
Since I wrote this answer we got even better builtins, that can finally let us reduce (no pun intended) the code to just 1 line!
const cartesian =
(...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Special thanks to inker for suggesting the use of reduce.
Special thanks to Bergi for suggesting the use of the newly added flatMap.
Special thanks to ECMAScript 2019 for adding flat and flatMap to the language!
Example
This is the exact example from your question:
let output = cartesian([1,2],[10,20],[100,200,300]);
Output
This is the output of that command:
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ]
Demo
See demos on:
JS Bin with Babel (for old browsers)
JS Bin without Babel (for modern browsers)
Syntax
The syntax that I used here is nothing new.
My example uses the spread operator and the rest parameters - features of JavaScript defined in the 6th edition of the ECMA-262 standard published on June 2015 and developed much earlier, better known as ES6 or ES2015. See:
http://www.ecma-international.org/ecma-262/6.0/
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
The new methods from the Update 2020 example was added in ES2019:
http://www.ecma-international.org/ecma-262/10.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
It makes code like this so simple that it's a sin not to use it. For old platforms that don't support it natively you can always use Babel or other tools to transpile it to older syntax - and in fact my example transpiled by Babel is still shorter and simpler than most of the examples here, but it doesn't really matter because the output of transpilation is not something that you need to understand or maintain, it's just a fact that I found interesting.
Conclusion
There's no need to write hundred of lines of code that is hard to maintain and there is no need to use entire libraries for such a simple thing, when two lines of vanilla JavaScript can easily get the job done. As you can see it really pays off to use modern features of the language and in cases where you need to support archaic platforms with no native support of the modern features you can always use Babel, TypeScript or other tools to transpile the new syntax to the old one.
Don't code like it's 1995
JavaScript evolves and it does so for a reason. TC39 does an amazing job of the language design with adding new features and the browser vendors do an amazing job of implementing those features.
To see the current state of native support of any given feature in the browsers, see:
http://caniuse.com/
https://kangax.github.io/compat-table/
To see the support in Node versions, see:
http://node.green/
To use modern syntax on platforms that don't support it natively, use Babel or TypeScript:
https://babeljs.io/
https://www.typescriptlang.org/
Here is a functional solution to the problem (without any mutable variable!) using reduce and flatten, provided by underscore.js:
function cartesianProductOf() {
return _.reduce(arguments, function(a, b) {
return _.flatten(_.map(a, function(x) {
return _.map(b, function(y) {
return x.concat([y]);
});
}), true);
}, [ [] ]);
}
// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>
Remark: This solution was inspired by http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
Here's a modified version of #viebel's code in plain Javascript, without using any library:
function cartesianProduct(arr) {
return arr.reduce(function(a,b){
return a.map(function(x){
return b.map(function(y){
return x.concat([y]);
})
}).reduce(function(a,b){ return a.concat(b) },[])
}, [[]])
}
var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));
The following efficient generator function returns the cartesian product of all given iterables:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));
It accepts arrays, strings, sets and all other objects implementing the iterable protocol.
Following the specification of the n-ary cartesian product it yields
[] if one or more given iterables are empty, e.g. [] or ''
[[a]] if a single iterable containing a single value a is given.
All other cases are handled as expected as demonstrated by the following test cases:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Test cases:
console.log([...cartesian([])]); // []
console.log([...cartesian([1])]); // [[1]]
console.log([...cartesian([1, 2])]); // [[1], [2]]
console.log([...cartesian([1], [])]); // []
console.log([...cartesian([1, 2], [])]); // []
console.log([...cartesian([1], [2])]); // [[1, 2]]
console.log([...cartesian([1], [2], [3])]); // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]); // [[1, 3], [2, 3], [1, 4], [2, 4]]
console.log([...cartesian('')]); // []
console.log([...cartesian('ab', 'c')]); // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]); // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]
console.log([...cartesian(new Set())]); // []
console.log([...cartesian(new Set([1]))]); // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]
It seems the community thinks this to be trivial and/or easy to find a reference implementation. However, upon brief inspection I couldn't find one, … either that or maybe it's just that I like re-inventing the wheel or solving classroom-like programming problems. Either way its your lucky day:
function cartProd(paramArray) {
function addTo(curr, args) {
var i, copy,
rest = args.slice(1),
last = !rest.length,
result = [];
for (i = 0; i < args[0].length; i++) {
copy = curr.slice();
copy.push(args[0][i]);
if (last) {
result.push(copy);
} else {
result = result.concat(addTo(copy, rest));
}
}
return result;
}
return addTo([], Array.prototype.slice.call(arguments));
}
>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
[1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100],
[1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200],
[2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
]
Full reference implementation that's relatively efficient… 😁
On efficiency: You could gain some by taking the if out of the loop and having 2 separate loops since it is technically constant and you'd be helping with branch prediction and all that mess, but that point is kind of moot in JavaScript.
Here's a non-fancy, straightforward recursive solution:
function cartesianProduct(a) { // a = array of array
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0]; // the first array of a
a = cartesianProduct(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length)
for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
console.log(cartesianProduct([[1, 2], [10, 20], [100, 200, 300]]));
// [
// [1,10,100],[1,10,200],[1,10,300],
// [1,20,100],[1,20,200],[1,20,300],
// [2,10,100],[2,10,200],[2,10,300],
// [2,20,100],[2,20,200],[2,20,300]
// ]
Here is a one-liner using the native ES2019 flatMap. No libraries needed, just a modern browser (or transpiler):
data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);
It's essentially a modern version of viebel's answer, without lodash.
Here is a recursive way that uses an ECMAScript 2015 generator function so you don't have to create all of the tuples at once:
function* cartesian() {
let arrays = arguments;
function* doCartesian(i, prod) {
if (i == arrays.length) {
yield prod;
} else {
for (let j = 0; j < arrays[i].length; j++) {
yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
}
}
}
yield* doCartesian(0, []);
}
console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));
This is a pure ES6 solution using arrow functions
function cartesianProduct(arr) {
return arr.reduce((a, b) =>
a.map(x => b.map(y => x.concat(y)))
.reduce((a, b) => a.concat(b), []), [[]]);
}
var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));
functional programming
This question is tagged functional-programming so let's take a look at the List monad:
One application for this monadic list is representing nondeterministic computation. List can hold results for all execution paths in an algorithm...
Well that sounds like a perfect fit for cartesian. JavaScript gives us Array and the monadic binding function is Array.prototype.flatMap, so let's put them to use -
const cartesian = (...all) => {
const loop = (t, a, ...more) =>
a === undefined
? [ t ]
: a.flatMap(x => loop([ ...t, x ], ...more))
return loop([], ...all)
}
console.log(cartesian([1,2], [10,20], [100,200,300]))
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
more recursion
Other recursive implementations include -
const cartesian = (a, ...more) =>
a == null
? [[]]
: cartesian(...more).flatMap(c => a.map(v => [v,...c]))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[2,10,100]
[1,20,100]
[2,20,100]
[1,10,200]
[2,10,200]
[1,20,200]
[2,20,200]
[1,10,300]
[2,10,300]
[1,20,300]
[2,20,300]
Note the different order above. You can get lexicographic order by inverting the two loops. Be careful not avoid duplicating work by calling cartesian inside the loop like Nick's answer -
const bind = (x, f) => f(x)
const cartesian = (a, ...more) =>
a == null
? [[]]
: bind(cartesian(...more), r => a.flatMap(v => r.map(c => [v,...c])))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
generators
Another option is to use generators. A generator is a good fit for combinatorics because the solution space can become very large. Generators offer lazy evaluation so they can be paused/resumed/canceled at any time -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const v of a)
for (const c of cartesian(...more)) // ⚠️
yield [v, ...c]
}
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
Maybe you saw that we called cartesian in a loop in the generator. If you suspect that can be optimized, it can! Here we use a generic tee function that forks any iterator n times -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const t of tee(cartesian(...more), a.length)) // ✅
for (const v of a)
for (const c of t) // ✅
yield [v, ...c]
}
Where tee is implemented as -
function tee(g, n = 2) {
const memo = []
function* iter(i) {
while (true) {
if (i >= memo.length) {
const w = g.next()
if (w.done) return
memo.push(w.value)
}
else yield memo[i++]
}
}
return Array.from(Array(n), _ => iter(0))
}
Even in small tests cartesian generator implemented with tee performs twice as fast.
Using a typical backtracking with ES6 generators,
function cartesianProduct(...arrays) {
let current = new Array(arrays.length);
return (function* backtracking(index) {
if(index == arrays.length) yield current.slice();
else for(let num of arrays[index]) {
current[index] = num;
yield* backtracking(index+1);
}
})(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }
Below there is a similar version compatible with older browsers.
function cartesianProduct(arrays) {
var result = [],
current = new Array(arrays.length);
(function backtracking(index) {
if(index == arrays.length) return result.push(current.slice());
for(var i=0; i<arrays[index].length; ++i) {
current[index] = arrays[index][i];
backtracking(index+1);
}
})(0);
return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
console.log('[' + item.join(', ') + ']');
});
div.as-console-wrapper { max-height: 100%; }
A single line approach, for better reading with indentations.
result = data.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
It takes a single array with arrays of wanted cartesian items.
var data = [[1, 2], [10, 20], [100, 200, 300]],
result = data.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; }
A coffeescript version with lodash:
_ = require("lodash")
cartesianProduct = ->
return _.reduceRight(arguments, (a,b) ->
_.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
, [ [] ])
For those who needs TypeScript (reimplemented #Danny's answer)
/**
* Calculates "Cartesian Product" sets.
* #example
* cartesianProduct([[1,2], [4,8], [16,32]])
* Returns:
* [
* [1, 4, 16],
* [1, 4, 32],
* [1, 8, 16],
* [1, 8, 32],
* [2, 4, 16],
* [2, 4, 32],
* [2, 8, 16],
* [2, 8, 32]
* ]
* #see https://stackoverflow.com/a/36234242/1955709
* #see https://en.wikipedia.org/wiki/Cartesian_product
* #param arr {T[][]}
* #returns {T[][]}
*/
function cartesianProduct<T> (arr: T[][]): T[][] {
return arr.reduce((a, b) => {
return a.map(x => {
return b.map(y => {
return x.concat(y)
})
}).reduce((c, d) => c.concat(d), [])
}, [[]] as T[][])
}
In my particular setting, the "old-fashioned" approach seemed to be more efficient than the methods based on more modern features. Below is the code (including a small comparison with other solutions posted in this thread by #rsp and #sebnukem) should it prove useful to someone else as well.
The idea is following. Let's say we are constructing the outer product of N arrays, a_1,...,a_N each of which has m_i components. The outer product of these arrays has M=m_1*m_2*...*m_N elements and we can identify each of them with a N-dimensional vector the components of which are positive integers and i-th component is strictly bounded from above by m_i. For example, the vector (0, 0, ..., 0) would correspond to the particular combination within which one takes the first element from each array, while (m_1-1, m_2-1, ..., m_N-1) is identified with the combination where one takes the last element from each array. Thus in order to construct all M combinations, the function below consecutively constructs all such vectors and for each of them identifies the corresponding combination of the elements of the input arrays.
function cartesianProduct(){
const N = arguments.length;
var arr_lengths = Array(N);
var digits = Array(N);
var num_tot = 1;
for(var i = 0; i < N; ++i){
const len = arguments[i].length;
if(!len){
num_tot = 0;
break;
}
digits[i] = 0;
num_tot *= (arr_lengths[i] = len);
}
var ret = Array(num_tot);
for(var num = 0; num < num_tot; ++num){
var item = Array(N);
for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
ret[num] = item;
for(var idx = 0; idx < N; ++idx){
if(digits[idx] == arr_lengths[idx]-1){
digits[idx] = 0;
}else{
digits[idx] += 1;
break;
}
}
}
return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0];
a = cartesianProduct_sebnukem(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length) for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];
let fns = {
'cartesianProduct': function(args){ return cartesianProduct(...args); },
'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};
Object.keys(fns).forEach(fname => {
console.time(fname);
const ret = fns[fname](args);
console.timeEnd(fname);
});
with node v6.12.2, I get following timings:
cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
You could reduce the 2D array. Use flatMap on the accumulator array to get acc.length x curr.length number of combinations in each loop. [].concat(c, n) is used because c is a number in the first iteration and an array afterwards.
const data = [ [1, 2], [10, 20], [100, 200, 300] ];
const output = data.reduce((acc, curr) =>
acc.flatMap(c => curr.map(n => [].concat(c, n)))
)
console.log(JSON.stringify(output))
(This is based on Nina Scholz's answer)
Here's a recursive one-liner that works using only flatMap and map:
const inp = [
[1, 2],
[10, 20],
[100, 200, 300]
];
const cartesian = (first, ...rest) =>
rest.length ? first.flatMap(v => cartesian(...rest).map(c => [v].concat(c)))
: first;
console.log(cartesian(...inp));
A few of the answers under this topic fail when any of the input arrays contains an array item. You you better check that.
Anyways no need for underscore, lodash whatsoever. I believe this one should do it with pure JS ES6, as functional as it gets.
This piece of code uses a reduce and a nested map, simply to get the cartesian product of two arrays however the second array comes from a recursive call to the same function with one less array; hence.. a[0].cartesian(...a.slice(1))
Array.prototype.cartesian = function(...a){
return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
: this;
};
var arr = ['a', 'b', 'c'],
brr = [1,2,3],
crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr)));
No libraries needed! :)
Needs arrow functions though and probably not that efficient. :/
const flatten = (xs) =>
xs.flat(Infinity)
const binaryCartesianProduct = (xs, ys) =>
xs.map((xi) => ys.map((yi) => [xi, yi])).flat()
const cartesianProduct = (...xss) =>
xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))
Modern JavaScript in just a few lines. No external libraries or dependencies like Lodash.
function cartesian(...arrays) {
return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}
console.log(
cartesian([1, 2], [10, 20], [100, 200, 300])
.map(arr => JSON.stringify(arr))
.join('\n')
);
A more readable implementation
function productOfTwo(one, two) {
return one.flatMap(x => two.map(y => [].concat(x, y)));
}
function product(head = [], ...tail) {
if (tail.length === 0) return head;
return productOfTwo(head, product(...tail));
}
const test = product(
[1, 2, 3],
['a', 'b']
);
console.log(JSON.stringify(test));
Another, even more simplified, 2021-style answer using only reduce, map, and concat methods:
const cartesian = (...arr) => arr.reduce((a,c) => a.map(e => c.map(f => e.concat([f]))).reduce((a,c) => a.concat(c), []), [[]]);
console.log(cartesian([1, 2], [10, 20], [100, 200, 300]));
For those happy with a ramda solution:
import { xprod, flatten } from 'ramda';
const cartessian = (...xs) => xs.reduce(xprod).map(flatten)
Or the same without dependencies and two lego blocks for free (xprod and flatten):
const flatten = xs => xs.flat();
const xprod = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]));
const cartessian = (...xs) => xs.reduce(xprod).map(flatten);
Just for a choice a real simple implementation using array's reduce:
const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");
const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);
A simple "mind and visually friendly" solution.
// t = [i, length]
const moveThreadForwardAt = (t, tCursor) => {
if (tCursor < 0)
return true; // reached end of first array
const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
t[tCursor][0] = newIndex;
if (newIndex == 0)
return moveThreadForwardAt(t, tCursor - 1);
return false;
}
const cartesianMult = (...args) => {
let result = [];
const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
let reachedEndOfFirstArray = false;
while (false == reachedEndOfFirstArray) {
result.push(t.map((v, i) => args[i][v[0]]));
reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
}
return result;
}
// cartesianMult(
// ['a1', 'b1', 'c1'],
// ['a2', 'b2'],
// ['a3', 'b3', 'c3'],
// ['a4', 'b4']
// );
console.log(cartesianMult(
['a1'],
['a2', 'b2'],
['a3', 'b3']
));
Yet another implementation. Not the shortest or fancy, but fast:
function cartesianProduct() {
var arr = [].slice.call(arguments),
intLength = arr.length,
arrHelper = [1],
arrToReturn = [];
for (var i = arr.length - 1; i >= 0; i--) {
arrHelper.unshift(arrHelper[0] * arr[i].length);
}
for (var i = 0, l = arrHelper[0]; i < l; i++) {
arrToReturn.push([]);
for (var j = 0; j < intLength; j++) {
arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
}
}
return arrToReturn;
}
A simple, modified version of #viebel's code in plain Javascript:
function cartesianProduct(...arrays) {
return arrays.reduce((a, b) => {
return [].concat(...a.map(x => {
const next = Array.isArray(x) ? x : [x];
return [].concat(b.map(y => next.concat(...[y])));
}));
});
}
const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);
console.log(product);
/*
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ];
*/
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))
This is for 3 arrays.
Some answers gave a way for any number of arrays.
This can easily contract or expand to less or more arrays.
I needed combinations of one set with repetitions, so I could have used:
f(a,a,a)
but used:
f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
A non-recursive approach that adds the ability to filter and modify the products before actually adding them to the result set.
Note: the use of .map rather than .forEach. In some browsers, .map runs faster.
function crossproduct(arrays, rowtest, rowaction) {
// Calculate the number of elements needed in the result
var result_elems = 1,
row_size = arrays.length;
arrays.map(function(array) {
result_elems *= array.length;
});
var temp = new Array(result_elems),
result = [];
// Go through each array and add the appropriate
// element to each element of the temp
var scale_factor = result_elems;
arrays.map(function(array) {
var set_elems = array.length;
scale_factor /= set_elems;
for (var i = result_elems - 1; i >= 0; i--) {
temp[i] = (temp[i] ? temp[i] : []);
var pos = i / scale_factor % set_elems;
// deal with floating point results for indexes,
// this took a little experimenting
if (pos < 1 || pos % 1 <= .5) {
pos = Math.floor(pos);
} else {
pos = Math.min(array.length - 1, Math.ceil(pos));
}
temp[i].push(array[pos]);
if (temp[i].length === row_size) {
var pass = (rowtest ? rowtest(temp[i]) : true);
if (pass) {
if (rowaction) {
result.push(rowaction(temp[i]));
} else {
result.push(temp[i]);
}
}
}
}
});
return result;
}
console.log(
crossproduct([[1, 2], [10, 20], [100, 200, 300]],null,null)
)
Similar in spirit to others, but highly readable imo.
function productOfTwo(a, b) {
return a.flatMap(c => b.map(d => [c, d].flat()));
}
[['a', 'b', 'c'], ['+', '-'], [1, 2, 3]].reduce(productOfTwo);
How would you implement the Cartesian product of multiple arrays in JavaScript?
As an example,
cartesian([1, 2], [10, 20], [100, 200, 300])
should return
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
2020 Update: 1-line (!) answer with vanilla JS
Original 2017 Answer: 2-line answer with vanilla JS:
(see updates below)
All of the answers here are overly complicated, most of them take 20 lines of code or even more.
This example uses just two lines of vanilla JavaScript, no lodash, underscore or other libraries:
let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;
Update:
This is the same as above but improved to strictly follow the Airbnb JavaScript Style Guide - validated using ESLint with eslint-config-airbnb-base:
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
Special thanks to ZuBB for letting me know about linter problems with the original code.
Update 2020:
Since I wrote this answer we got even better builtins, that can finally let us reduce (no pun intended) the code to just 1 line!
const cartesian =
(...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Special thanks to inker for suggesting the use of reduce.
Special thanks to Bergi for suggesting the use of the newly added flatMap.
Special thanks to ECMAScript 2019 for adding flat and flatMap to the language!
Example
This is the exact example from your question:
let output = cartesian([1,2],[10,20],[100,200,300]);
Output
This is the output of that command:
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ]
Demo
See demos on:
JS Bin with Babel (for old browsers)
JS Bin without Babel (for modern browsers)
Syntax
The syntax that I used here is nothing new.
My example uses the spread operator and the rest parameters - features of JavaScript defined in the 6th edition of the ECMA-262 standard published on June 2015 and developed much earlier, better known as ES6 or ES2015. See:
http://www.ecma-international.org/ecma-262/6.0/
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
The new methods from the Update 2020 example was added in ES2019:
http://www.ecma-international.org/ecma-262/10.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
It makes code like this so simple that it's a sin not to use it. For old platforms that don't support it natively you can always use Babel or other tools to transpile it to older syntax - and in fact my example transpiled by Babel is still shorter and simpler than most of the examples here, but it doesn't really matter because the output of transpilation is not something that you need to understand or maintain, it's just a fact that I found interesting.
Conclusion
There's no need to write hundred of lines of code that is hard to maintain and there is no need to use entire libraries for such a simple thing, when two lines of vanilla JavaScript can easily get the job done. As you can see it really pays off to use modern features of the language and in cases where you need to support archaic platforms with no native support of the modern features you can always use Babel, TypeScript or other tools to transpile the new syntax to the old one.
Don't code like it's 1995
JavaScript evolves and it does so for a reason. TC39 does an amazing job of the language design with adding new features and the browser vendors do an amazing job of implementing those features.
To see the current state of native support of any given feature in the browsers, see:
http://caniuse.com/
https://kangax.github.io/compat-table/
To see the support in Node versions, see:
http://node.green/
To use modern syntax on platforms that don't support it natively, use Babel or TypeScript:
https://babeljs.io/
https://www.typescriptlang.org/
Here is a functional solution to the problem (without any mutable variable!) using reduce and flatten, provided by underscore.js:
function cartesianProductOf() {
return _.reduce(arguments, function(a, b) {
return _.flatten(_.map(a, function(x) {
return _.map(b, function(y) {
return x.concat([y]);
});
}), true);
}, [ [] ]);
}
// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>
Remark: This solution was inspired by http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
Here's a modified version of #viebel's code in plain Javascript, without using any library:
function cartesianProduct(arr) {
return arr.reduce(function(a,b){
return a.map(function(x){
return b.map(function(y){
return x.concat([y]);
})
}).reduce(function(a,b){ return a.concat(b) },[])
}, [[]])
}
var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));
The following efficient generator function returns the cartesian product of all given iterables:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));
It accepts arrays, strings, sets and all other objects implementing the iterable protocol.
Following the specification of the n-ary cartesian product it yields
[] if one or more given iterables are empty, e.g. [] or ''
[[a]] if a single iterable containing a single value a is given.
All other cases are handled as expected as demonstrated by the following test cases:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Test cases:
console.log([...cartesian([])]); // []
console.log([...cartesian([1])]); // [[1]]
console.log([...cartesian([1, 2])]); // [[1], [2]]
console.log([...cartesian([1], [])]); // []
console.log([...cartesian([1, 2], [])]); // []
console.log([...cartesian([1], [2])]); // [[1, 2]]
console.log([...cartesian([1], [2], [3])]); // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]); // [[1, 3], [2, 3], [1, 4], [2, 4]]
console.log([...cartesian('')]); // []
console.log([...cartesian('ab', 'c')]); // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]); // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]
console.log([...cartesian(new Set())]); // []
console.log([...cartesian(new Set([1]))]); // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]
It seems the community thinks this to be trivial and/or easy to find a reference implementation. However, upon brief inspection I couldn't find one, … either that or maybe it's just that I like re-inventing the wheel or solving classroom-like programming problems. Either way its your lucky day:
function cartProd(paramArray) {
function addTo(curr, args) {
var i, copy,
rest = args.slice(1),
last = !rest.length,
result = [];
for (i = 0; i < args[0].length; i++) {
copy = curr.slice();
copy.push(args[0][i]);
if (last) {
result.push(copy);
} else {
result = result.concat(addTo(copy, rest));
}
}
return result;
}
return addTo([], Array.prototype.slice.call(arguments));
}
>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
[1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100],
[1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200],
[2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
]
Full reference implementation that's relatively efficient… 😁
On efficiency: You could gain some by taking the if out of the loop and having 2 separate loops since it is technically constant and you'd be helping with branch prediction and all that mess, but that point is kind of moot in JavaScript.
Here's a non-fancy, straightforward recursive solution:
function cartesianProduct(a) { // a = array of array
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0]; // the first array of a
a = cartesianProduct(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length)
for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
console.log(cartesianProduct([[1, 2], [10, 20], [100, 200, 300]]));
// [
// [1,10,100],[1,10,200],[1,10,300],
// [1,20,100],[1,20,200],[1,20,300],
// [2,10,100],[2,10,200],[2,10,300],
// [2,20,100],[2,20,200],[2,20,300]
// ]
Here is a one-liner using the native ES2019 flatMap. No libraries needed, just a modern browser (or transpiler):
data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);
It's essentially a modern version of viebel's answer, without lodash.
Here is a recursive way that uses an ECMAScript 2015 generator function so you don't have to create all of the tuples at once:
function* cartesian() {
let arrays = arguments;
function* doCartesian(i, prod) {
if (i == arrays.length) {
yield prod;
} else {
for (let j = 0; j < arrays[i].length; j++) {
yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
}
}
}
yield* doCartesian(0, []);
}
console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));
This is a pure ES6 solution using arrow functions
function cartesianProduct(arr) {
return arr.reduce((a, b) =>
a.map(x => b.map(y => x.concat(y)))
.reduce((a, b) => a.concat(b), []), [[]]);
}
var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));
functional programming
This question is tagged functional-programming so let's take a look at the List monad:
One application for this monadic list is representing nondeterministic computation. List can hold results for all execution paths in an algorithm...
Well that sounds like a perfect fit for cartesian. JavaScript gives us Array and the monadic binding function is Array.prototype.flatMap, so let's put them to use -
const cartesian = (...all) => {
const loop = (t, a, ...more) =>
a === undefined
? [ t ]
: a.flatMap(x => loop([ ...t, x ], ...more))
return loop([], ...all)
}
console.log(cartesian([1,2], [10,20], [100,200,300]))
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
more recursion
Other recursive implementations include -
const cartesian = (a, ...more) =>
a == null
? [[]]
: cartesian(...more).flatMap(c => a.map(v => [v,...c]))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[2,10,100]
[1,20,100]
[2,20,100]
[1,10,200]
[2,10,200]
[1,20,200]
[2,20,200]
[1,10,300]
[2,10,300]
[1,20,300]
[2,20,300]
Note the different order above. You can get lexicographic order by inverting the two loops. Be careful not avoid duplicating work by calling cartesian inside the loop like Nick's answer -
const bind = (x, f) => f(x)
const cartesian = (a, ...more) =>
a == null
? [[]]
: bind(cartesian(...more), r => a.flatMap(v => r.map(c => [v,...c])))
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
generators
Another option is to use generators. A generator is a good fit for combinatorics because the solution space can become very large. Generators offer lazy evaluation so they can be paused/resumed/canceled at any time -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const v of a)
for (const c of cartesian(...more)) // ⚠️
yield [v, ...c]
}
for (const p of cartesian([1,2], [10,20], [100,200,300]))
console.log(JSON.stringify(p))
.as-console-wrapper { min-height: 100%; top: 0; }
[1,10,100]
[1,10,200]
[1,10,300]
[1,20,100]
[1,20,200]
[1,20,300]
[2,10,100]
[2,10,200]
[2,10,300]
[2,20,100]
[2,20,200]
[2,20,300]
Maybe you saw that we called cartesian in a loop in the generator. If you suspect that can be optimized, it can! Here we use a generic tee function that forks any iterator n times -
function* cartesian(a, ...more) {
if (a == null) return yield []
for (const t of tee(cartesian(...more), a.length)) // ✅
for (const v of a)
for (const c of t) // ✅
yield [v, ...c]
}
Where tee is implemented as -
function tee(g, n = 2) {
const memo = []
function* iter(i) {
while (true) {
if (i >= memo.length) {
const w = g.next()
if (w.done) return
memo.push(w.value)
}
else yield memo[i++]
}
}
return Array.from(Array(n), _ => iter(0))
}
Even in small tests cartesian generator implemented with tee performs twice as fast.
Using a typical backtracking with ES6 generators,
function cartesianProduct(...arrays) {
let current = new Array(arrays.length);
return (function* backtracking(index) {
if(index == arrays.length) yield current.slice();
else for(let num of arrays[index]) {
current[index] = num;
yield* backtracking(index+1);
}
})(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }
Below there is a similar version compatible with older browsers.
function cartesianProduct(arrays) {
var result = [],
current = new Array(arrays.length);
(function backtracking(index) {
if(index == arrays.length) return result.push(current.slice());
for(var i=0; i<arrays[index].length; ++i) {
current[index] = arrays[index][i];
backtracking(index+1);
}
})(0);
return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
console.log('[' + item.join(', ') + ']');
});
div.as-console-wrapper { max-height: 100%; }
A single line approach, for better reading with indentations.
result = data.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
It takes a single array with arrays of wanted cartesian items.
var data = [[1, 2], [10, 20], [100, 200, 300]],
result = data.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; }
A coffeescript version with lodash:
_ = require("lodash")
cartesianProduct = ->
return _.reduceRight(arguments, (a,b) ->
_.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
, [ [] ])
For those who needs TypeScript (reimplemented #Danny's answer)
/**
* Calculates "Cartesian Product" sets.
* #example
* cartesianProduct([[1,2], [4,8], [16,32]])
* Returns:
* [
* [1, 4, 16],
* [1, 4, 32],
* [1, 8, 16],
* [1, 8, 32],
* [2, 4, 16],
* [2, 4, 32],
* [2, 8, 16],
* [2, 8, 32]
* ]
* #see https://stackoverflow.com/a/36234242/1955709
* #see https://en.wikipedia.org/wiki/Cartesian_product
* #param arr {T[][]}
* #returns {T[][]}
*/
function cartesianProduct<T> (arr: T[][]): T[][] {
return arr.reduce((a, b) => {
return a.map(x => {
return b.map(y => {
return x.concat(y)
})
}).reduce((c, d) => c.concat(d), [])
}, [[]] as T[][])
}
In my particular setting, the "old-fashioned" approach seemed to be more efficient than the methods based on more modern features. Below is the code (including a small comparison with other solutions posted in this thread by #rsp and #sebnukem) should it prove useful to someone else as well.
The idea is following. Let's say we are constructing the outer product of N arrays, a_1,...,a_N each of which has m_i components. The outer product of these arrays has M=m_1*m_2*...*m_N elements and we can identify each of them with a N-dimensional vector the components of which are positive integers and i-th component is strictly bounded from above by m_i. For example, the vector (0, 0, ..., 0) would correspond to the particular combination within which one takes the first element from each array, while (m_1-1, m_2-1, ..., m_N-1) is identified with the combination where one takes the last element from each array. Thus in order to construct all M combinations, the function below consecutively constructs all such vectors and for each of them identifies the corresponding combination of the elements of the input arrays.
function cartesianProduct(){
const N = arguments.length;
var arr_lengths = Array(N);
var digits = Array(N);
var num_tot = 1;
for(var i = 0; i < N; ++i){
const len = arguments[i].length;
if(!len){
num_tot = 0;
break;
}
digits[i] = 0;
num_tot *= (arr_lengths[i] = len);
}
var ret = Array(num_tot);
for(var num = 0; num < num_tot; ++num){
var item = Array(N);
for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
ret[num] = item;
for(var idx = 0; idx < N; ++idx){
if(digits[idx] == arr_lengths[idx]-1){
digits[idx] = 0;
}else{
digits[idx] += 1;
break;
}
}
}
return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
var i, j, l, m, a1, o = [];
if (!a || a.length == 0) return a;
a1 = a.splice(0, 1)[0];
a = cartesianProduct_sebnukem(a);
for (i = 0, l = a1.length; i < l; i++) {
if (a && a.length) for (j = 0, m = a.length; j < m; j++)
o.push([a1[i]].concat(a[j]));
else
o.push([a1[i]]);
}
return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];
let fns = {
'cartesianProduct': function(args){ return cartesianProduct(...args); },
'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};
Object.keys(fns).forEach(fname => {
console.time(fname);
const ret = fns[fname](args);
console.timeEnd(fname);
});
with node v6.12.2, I get following timings:
cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
You could reduce the 2D array. Use flatMap on the accumulator array to get acc.length x curr.length number of combinations in each loop. [].concat(c, n) is used because c is a number in the first iteration and an array afterwards.
const data = [ [1, 2], [10, 20], [100, 200, 300] ];
const output = data.reduce((acc, curr) =>
acc.flatMap(c => curr.map(n => [].concat(c, n)))
)
console.log(JSON.stringify(output))
(This is based on Nina Scholz's answer)
Here's a recursive one-liner that works using only flatMap and map:
const inp = [
[1, 2],
[10, 20],
[100, 200, 300]
];
const cartesian = (first, ...rest) =>
rest.length ? first.flatMap(v => cartesian(...rest).map(c => [v].concat(c)))
: first;
console.log(cartesian(...inp));
A few of the answers under this topic fail when any of the input arrays contains an array item. You you better check that.
Anyways no need for underscore, lodash whatsoever. I believe this one should do it with pure JS ES6, as functional as it gets.
This piece of code uses a reduce and a nested map, simply to get the cartesian product of two arrays however the second array comes from a recursive call to the same function with one less array; hence.. a[0].cartesian(...a.slice(1))
Array.prototype.cartesian = function(...a){
return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
: this;
};
var arr = ['a', 'b', 'c'],
brr = [1,2,3],
crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr)));
No libraries needed! :)
Needs arrow functions though and probably not that efficient. :/
const flatten = (xs) =>
xs.flat(Infinity)
const binaryCartesianProduct = (xs, ys) =>
xs.map((xi) => ys.map((yi) => [xi, yi])).flat()
const cartesianProduct = (...xss) =>
xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))
Modern JavaScript in just a few lines. No external libraries or dependencies like Lodash.
function cartesian(...arrays) {
return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}
console.log(
cartesian([1, 2], [10, 20], [100, 200, 300])
.map(arr => JSON.stringify(arr))
.join('\n')
);
A more readable implementation
function productOfTwo(one, two) {
return one.flatMap(x => two.map(y => [].concat(x, y)));
}
function product(head = [], ...tail) {
if (tail.length === 0) return head;
return productOfTwo(head, product(...tail));
}
const test = product(
[1, 2, 3],
['a', 'b']
);
console.log(JSON.stringify(test));
Another, even more simplified, 2021-style answer using only reduce, map, and concat methods:
const cartesian = (...arr) => arr.reduce((a,c) => a.map(e => c.map(f => e.concat([f]))).reduce((a,c) => a.concat(c), []), [[]]);
console.log(cartesian([1, 2], [10, 20], [100, 200, 300]));
For those happy with a ramda solution:
import { xprod, flatten } from 'ramda';
const cartessian = (...xs) => xs.reduce(xprod).map(flatten)
Or the same without dependencies and two lego blocks for free (xprod and flatten):
const flatten = xs => xs.flat();
const xprod = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]));
const cartessian = (...xs) => xs.reduce(xprod).map(flatten);
Just for a choice a real simple implementation using array's reduce:
const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");
const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);
A simple "mind and visually friendly" solution.
// t = [i, length]
const moveThreadForwardAt = (t, tCursor) => {
if (tCursor < 0)
return true; // reached end of first array
const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
t[tCursor][0] = newIndex;
if (newIndex == 0)
return moveThreadForwardAt(t, tCursor - 1);
return false;
}
const cartesianMult = (...args) => {
let result = [];
const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
let reachedEndOfFirstArray = false;
while (false == reachedEndOfFirstArray) {
result.push(t.map((v, i) => args[i][v[0]]));
reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
}
return result;
}
// cartesianMult(
// ['a1', 'b1', 'c1'],
// ['a2', 'b2'],
// ['a3', 'b3', 'c3'],
// ['a4', 'b4']
// );
console.log(cartesianMult(
['a1'],
['a2', 'b2'],
['a3', 'b3']
));
Yet another implementation. Not the shortest or fancy, but fast:
function cartesianProduct() {
var arr = [].slice.call(arguments),
intLength = arr.length,
arrHelper = [1],
arrToReturn = [];
for (var i = arr.length - 1; i >= 0; i--) {
arrHelper.unshift(arrHelper[0] * arr[i].length);
}
for (var i = 0, l = arrHelper[0]; i < l; i++) {
arrToReturn.push([]);
for (var j = 0; j < intLength; j++) {
arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
}
}
return arrToReturn;
}
A simple, modified version of #viebel's code in plain Javascript:
function cartesianProduct(...arrays) {
return arrays.reduce((a, b) => {
return [].concat(...a.map(x => {
const next = Array.isArray(x) ? x : [x];
return [].concat(b.map(y => next.concat(...[y])));
}));
});
}
const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);
console.log(product);
/*
[ [ 1, 10, 100 ],
[ 1, 10, 200 ],
[ 1, 10, 300 ],
[ 1, 20, 100 ],
[ 1, 20, 200 ],
[ 1, 20, 300 ],
[ 2, 10, 100 ],
[ 2, 10, 200 ],
[ 2, 10, 300 ],
[ 2, 20, 100 ],
[ 2, 20, 200 ],
[ 2, 20, 300 ] ];
*/
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))
This is for 3 arrays.
Some answers gave a way for any number of arrays.
This can easily contract or expand to less or more arrays.
I needed combinations of one set with repetitions, so I could have used:
f(a,a,a)
but used:
f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
A non-recursive approach that adds the ability to filter and modify the products before actually adding them to the result set.
Note: the use of .map rather than .forEach. In some browsers, .map runs faster.
function crossproduct(arrays, rowtest, rowaction) {
// Calculate the number of elements needed in the result
var result_elems = 1,
row_size = arrays.length;
arrays.map(function(array) {
result_elems *= array.length;
});
var temp = new Array(result_elems),
result = [];
// Go through each array and add the appropriate
// element to each element of the temp
var scale_factor = result_elems;
arrays.map(function(array) {
var set_elems = array.length;
scale_factor /= set_elems;
for (var i = result_elems - 1; i >= 0; i--) {
temp[i] = (temp[i] ? temp[i] : []);
var pos = i / scale_factor % set_elems;
// deal with floating point results for indexes,
// this took a little experimenting
if (pos < 1 || pos % 1 <= .5) {
pos = Math.floor(pos);
} else {
pos = Math.min(array.length - 1, Math.ceil(pos));
}
temp[i].push(array[pos]);
if (temp[i].length === row_size) {
var pass = (rowtest ? rowtest(temp[i]) : true);
if (pass) {
if (rowaction) {
result.push(rowaction(temp[i]));
} else {
result.push(temp[i]);
}
}
}
}
});
return result;
}
console.log(
crossproduct([[1, 2], [10, 20], [100, 200, 300]],null,null)
)
Similar in spirit to others, but highly readable imo.
function productOfTwo(a, b) {
return a.flatMap(c => b.map(d => [c, d].flat()));
}
[['a', 'b', 'c'], ['+', '-'], [1, 2, 3]].reduce(productOfTwo);