Implementing MergeSort on an array of two-element arrays in JavaScript - javascript

I'm trying to use merge sort to sort an array of edge sets, which themselves are represented as 2-element arrays, i.e. the edge EC is ['E', 'C'].
The array I'm trying to sort is [['D', 'F'], ['A', 'D'], ['F', 'I'], ['B', 'E'], ['B', 'J'], ['A', 'C'], ['E', 'G'], ['A', 'J'], ['G', 'H']]. And I want it to sort by the 'from' edge first, and then if two edges have the same 'from' edge, by the 'to' (second) edge.
When I run the following in Firebug, it looks like it's working (from the things I'm printing to the console), but then at the end it gives ['AC', 'AC', 'AC', 'AC', 'AC', ...].
Array.prototype.toString = function(){
var s = "[";
if(this.length > 0){
s += this[0].toString();
for(var i = 1; i < this.length; i++){
s += ", " + this[i].toString();
}
}
s += "]";
return s;
}
var edges = [['D', 'F'], ['A', 'D'], ['F', 'I'], ['B', 'E'], ['B', 'J'],
['A', 'C'], ['E', 'G'], ['A', 'J'], ['G', 'H']];
function sortEdges(edges){
// mergesort
// split up
if(edges.length < 2){
return edges;
} else {
var fH = edges.slice(0, Math.floor(edges.length / 2)); // fH: firstHalf
var sH = edges.slice(Math.floor(edges.length / 2), edges.length); // sH: firstHalf
console.log(fH.toString());
console.log(sH.toString());
fH = sortEdges(fH);
sH = sortEdges(sH);
// merge
var fHC = 0; // fHC: firstHalfCounter
var sHC = 0; // sHC: secondHalfCounter
var bothHalves = new Array();
for(var i = 0; i < edges.length; i++){
console.log("fHC: " + fHC + ", sHC: " + sHC + ", bothHalves: " + bothHalves.toString());
if(fHC < fH.length && (sHC >= sH.length || fH[fHC][0] < sH[sHC][0])){
// compare 'from' vertex
bothHalves.push(fH[fHC]);
fHC++;
} else if(fHC < fH.length && fH[fHC][0] == sH[sHC][0]){
// if tied, compare 'to' vertex
if(fH[fHC][1] <= sH[sHC][1]){
bothHalves.push(fH[fHC]);
fHC++;
} else {
bothHalves.push(sH[sHC]);
sHC;
}
} else {
bothHalves.push(sH[sHC]);
sHC++;
}
}
return bothHalves;
}
}
edges = sortEdges(edges);
console.log(edges.toString());

You left out an increment:
Array.prototype.toString = function(){
var s = "[";
if(this.length > 0){
s += this[0].toString();
for(var i = 1; i < this.length; i++){
s += ", " + this[i].toString();
}
}
s += "]";
return s;
}
var edges = [['D', 'F'], ['A', 'D'], ['F', 'I'], ['B', 'E'], ['B', 'J'],
['A', 'C'], ['E', 'G'], ['A', 'J'], ['G', 'H']];
function sortEdges(edges){
// mergesort
// split up
if(edges.length < 2){
return edges;
} else {
var fH = edges.slice(0, Math.floor(edges.length / 2)); // fH: firstHalf
var sH = edges.slice(Math.floor(edges.length / 2), edges.length); // sH: firstHalf
console.log(fH.toString());
console.log(sH.toString());
fH = sortEdges(fH);
sH = sortEdges(sH);
// merge
var fHC = 0; // fHC: firstHalfCounter
var sHC = 0; // sHC: secondHalfCounter
var bothHalves = new Array();
for(var i = 0; i < edges.length; i++){
console.log("fHC: " + fHC + ", sHC: " + sHC + ", bothHalves: " + bothHalves.toString());
if(fHC < fH.length && (sHC >= sH.length || fH[fHC][0] < sH[sHC][0])){
// compare 'from' vertex
bothHalves.push(fH[fHC]);
fHC++;
} else if(fHC < fH.length && fH[fHC][0] == sH[sHC][0]){
// if tied, compare 'to' vertex
if(fH[fHC][1] <= sH[sHC][1]){
bothHalves.push(fH[fHC]);
fHC++;
} else {
bothHalves.push(sH[sHC]);
sHC++;
// ^^ You left out this increment <--------------HERE----------------
}
} else {
bothHalves.push(sH[sHC]);
sHC++;
}
}
return bothHalves;
}
}
edges = sortEdges(edges);
console.log(edges.toString());

Related

checking pair value in array

here i wanna ask about how to check the data in the array if not same value/data on next index, push it on new array,
here is the example:
function check(arr){
let text = "";
let newArr = [];
for(let i = 0 ; i < arr.length-1 ; i++){
if(arr[i] !== arr[i+1]){
text = arr[i] + ' and ' + arr[i+1];
newArr.push(text)
}
}
return newArr
};
console.log(check([ 'A', 'A', 'M', 'Y', 'I', 'W', 'W', 'M', 'R', 'Y' ]))
// output "A and M", "A and Y", "I and W", "W and M", "R and Y"]
console.log(check([ 'a', 'b', 'j', 'j', 'i', 't' ]))
my result here is not i want, it was repeated the data which i already push . in newArr
i want the ouput like this :
["A and M", "A and Y", "I and W", "W and M", "R and Y"]
because of each array not the same initial,
i hope this question makes sense
You can do the following :
function check(arr) {
let text = "";
let newArr = [];
for (let i = 0, next = 0; i < arr.length; i++) {
if (next == 2) {
next = 0;
i += 2;
}
if (arr[i + 1] !== undefined) {
if (arr[i + 2] !== undefined) {
text = arr[i] + ' and ' + arr[i + 2];
} else {
text = arr[i] + ' and ' + arr[i + 1];
}
newArr.push(text)
}
next += 1;
}
return newArr
};
console.log(check(['A', 'A', 'M', 'Y', 'I', 'W', 'W', 'M', 'R', 'Y']))
console.log(check(['a', 'b', 'j', 'j', 'i', 't']))

Reorder the objects in the array

I have arrays as following
var A = ['C', 'D', 'E', 'F', 'G'];
var B = [3, 0, 4, 1, 2];
I need to rearrange array A with the given index values in array B. My solution to the problem is following
function reArrange(A,B){
var num;
var letter;
for(var i = 0; i < A.length; i++){
num = B[i];
letter = A[i];
A[num] = letter;
}
return A;
}
reArrange(A, B);
I get an output of ['D', 'C', 'E', 'C', 'E'] when it should be ['D', 'F', 'G', 'C', 'E']
Hope this will help.
var A = ['C', 'D', 'E', 'F', 'G'];
var B = [3, 0, 4, 1, 2];
var C = []
function reArrange(A,B){
var num;
var letter;
for(var i = 0; i < A.length; i++){
num = B[i];
letter = A[i];
C[num] = letter;
}
return C;
}
reArrange(A, B);
console.log(C)
You could use the second array for the indices for assinging the actual value of the given array.
var array = ['C', 'D', 'E', 'F', 'G'],
order = [3, 0, 4, 1, 2],
result = order.reduce((r, a, i) => (r[a] = array[i], r), []);
console.log(result); // ['D', 'F', 'G', 'C', 'E']
Simple and short
var A = ['C', 'D', 'E', 'F', 'G'];
var B = [3, 0, 4, 1, 2];
function reorderAB(first, second){
var result= new Array();
for(var i=0;i<second.length;i++){
result[second[i]]=first[i];
}
console.log(result);
}
reorderAB(A,B);
The problem is that you replaced array A with new value.
Example with the first i in your loop.
i = 0, num = 3 and letter = C
The original A[3] = F
Then you assign A[num] = letter, mean that A[3] (F) has new value/replaced C and so on.
You can create a Map from B -> A and then order the keys accordingly to output your expected array, however I am not sure what use case you are using it for.
const A = ['C', 'D', 'E', 'F', 'G'];
const B = [3, 0, 4, 1, 2];
let bToA = new Map();
B.forEach((a, i) => bToA.set(a, A[i]))
const final = [...bToA.keys()].sort().map(x => bToA.get(x))
console.log(final);

Fetching JavaScript array elements after consecutive occurrence of an element

I have a JavaScript array like:
var myArray = ['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'];
I want to fetch only those elements of the array that come after 2 consequent occurrences of a particular element.
i.e. in the above array, I want to fetch all the elements that come after consequent 'x', 'x'
So my output should be:
'p'
'b'
I have a solution like :
var arrLength = myArray.length;
for (var i = 0; i < arrLength; i++) {
if(i+2 < arrLength && myArray[i] == 'x' && myArray[i+1] == 'x') {
console.log(myArray[i+2]);
}
};
This satisfies my needs, but it is not so generic.
For eg. if I have to check for 3 consequent occurrences, then again I have to add a condition inside if for myArray[i+2] == 'x' and so on.
Could anyone provide a better way to fetch the elements?
The functional way would be to use recursion. With an ES6 spread, you can pretty much emulate the terseness of a truly 'functional' language :-)
var myArray = ['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'];
function reducer(acc, xs) {
if (xs.length > 2) {
if (xs[0] === xs[1]) {
// add the third element to accumulator
// remove first three elements from xs
// return reducer([xs[2], ...acc], xs.slice(3));
// or per Nina's question below
return reducer([xs[2], ...acc], xs.slice(1));
} else {
// remove first element from xs and recurse
return reducer(acc, xs.slice(1))
}
} else {
return acc;
}
}
console.log(reducer([], myArray));
A generic straight forward approach for any comparable content.
function getParts(array, pattern) {
return array.reduce(function (r, a, i) {
i >= pattern.length && pattern.every(function (b, j) {
return b === array[i + j - pattern.length];
}) && r.push(a);
return r;
}, []);
}
function p(o) {
document.write('<pre>' + JSON.stringify(o, 0, 4) + '</pre>');
}
p(getParts(['a', 'x', 'x', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'], ['x', 'x']));
p(getParts(['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'], ['a', 'x', 'b']));
p(getParts(['a', 'b', 'c', 'd', 'z', 'y', 'a', 'b', 'c', 'd', 'x', 'x'], ['a', 'b', 'c', 'd']));
p(getParts([41, 23, 3, 7, 8, 11, 56, 33, 7, 8, 11, 2, 5], [7, 8, 11]));
You can try following logic
var myArray = ['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'];
function search(ch, times) {
var splitStr = "";
for(var i = 0; i < times; i++) {
splitStr += ch;
} // Generate the split string xx in the above case.
var str = myArray.join(''); // Join array items into a string
var array = str.split(splitStr); // Split the string based on split string
var result = {};
// iterate on the array starting from index 1 as at index 0 will be string before split str
for (var i = 1 ; i < array.length; i++) {
if(array[i] !== "") {
result[array[i].substring(0,1)] = ''; // A map in order to avoid duplicate values
}
}
return Object.keys(result); // return the keys
}
console.dir(search('x',2));
Here is a straightforward iterative solution. We maintain an array consecutive of consecutive elements. If that array gets to length 2, then the next element is printed and consecutive is reset.
var arr = ['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'];
var REPEATS_NEEDED = 2;
var consecutive = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (consecutive.length === REPEATS_NEEDED) {
console.log(arr[i]);
consecutive = [arr[i]];
continue;
}
// either add to or reset 'consecutive'
if (arr[i] === consecutive[0]) {
consecutive.push(arr[i]);
} else {
consecutive = [arr[i]];
}
};
You can create an additional function isItGood like this:
var myArray = ['a', 'x', 'b', 'x', 'x', 'p', 'y', 'x', 'x', 'b', 'x', 'x'];
var arrLength = myArray.length;
for (var i = 0; i < arrLength; i++) {
isItGood(myArray, i, 'x', 2);
};
function isItGood(arr, i, elem, total) {
for ( var j = 0 ; j < total ; j++ ) {
if ( i + total >= arr.length || arr[i+j] != elem ) {
return;
}
}
console.log(arr[i+total]);
// just to see the result (no need to open a console)
document.getElementById('p').innerHTML+=("<br/>"+arr[i+total]);
}
<p id="p">Result: </p>
If I had to write this in Scala instead of JavaScript I could just do it in one line.
myArray.sliding(3).filter(l => l(0) == 'x' && l(1) == 'x').map(l => l(2))
So I guess I could do it the same way in JS if I implement the sliding function myself.
e.g.
function sliding(array, n, step) {
if(!step) step = 1;
var r = [];
for(var i = 0; i < array.length - n + 1; i += step) {
r.push(array.slice(i, i + n));
}
return r;
}
var result = sliding(myArray, 3).filter(l => l[0] === "x" && l[1] === "x").map(l => l[2]);
The only downside here is that this runs slower than a more iterative approach. But that only matters for very big arrays.
Try using for loop using variables referencing previous index, current index, next index of array
var myArray = ["a", "x", "b", "x", "x", "p", "y", "x", "x", "b", "x", "x"];
for (var res = [], curr = 0, prev = curr - 1, match = curr + 1
; curr < myArray.length - 1; curr++, prev++, match++) {
if (myArray[curr] === myArray[prev]) res.push(myArray[match]);
};
console.log(res);
document.body.textContent = res;

Split array into two arrays

var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var point = 'c';
How can I split the "arr" into two arrays based on the "point" variable, like:
['a', 'b']
and
['d', 'e', 'f']
var arr2 = ['a', 'b', 'c', 'd', 'e', 'f'];
arr = arr2.splice(0, arr2.indexOf('c'));
To remove 'c' from arr2:
arr2.splice(0,1);
arr contains the first two elements and arr2 contains the last three.
This makes some assumptions (like arr2 will always contain the 'point' at first assignment), so add some correctness checking for border cases as necessary.
Use indexOf and slice
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var indexToSplit = arr.indexOf('c');
var first = arr.slice(0, indexToSplit);
var second = arr.slice(indexToSplit + 1);
console.log({first, second});
Sharing this convenience function that I ended up making after visiting this page.
function chunkArray(arr,n){
var chunkLength = Math.max(arr.length/n ,1);
var chunks = [];
for (var i = 0; i < n; i++) {
if(chunkLength*(i+1)<=arr.length)chunks.push(arr.slice(chunkLength*i, chunkLength*(i+1)));
}
return chunks;
}
Sample usage:
chunkArray([1,2,3,4,5,6],2);
//returns [[1,2,3],[4,5,6]]
chunkArray([1,2,3,4,5,6,7],2);
//returns [[1,2,3],[4,5,6,7]]
chunkArray([1,2,3,4,5,6],3);
//returns [[1,2],[3,4],[5,6]]
chunkArray([1,2,3,4,5,6,7,8],3);
//returns [[1,2],[3,4,5],[6,7,8]]
chunkArray([1,2,3,4,5,6,7,8],42);//over chunk
//returns [[1],[2],[3],[4],[5],[6],[7],[8]]
Try this one:
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var point = 'c';
var idx = arr.indexOf(point);
arr.slice(0, idx) // ["a", "b"]
arr.slice(idx + 1) // ["d", "e", "f"]
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var point = 'c';
Array.prototype.exists = function(search){
for (var i=0; i<this.length; i++) {
if (this[i] == search) return i;
}
return false;
}
if(i=arr.exists(point))
{
var neewArr=arr.splice(i);
neewArr.shift(0);
console.log(arr); // output: ["a", "b"]
console.log(neewArr); // output: ["d", "e", "f"]
}​
Here is an example.
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var point = 'c';
var i = arr.indexOf(point);
var firstHalf, secondHalf, end, start;
if (i>0) {
firstHalf = arr.slice(0, i);
secondHalf = arr.slice(i + 1, arr.length);
}
//this should get you started. Can you think of what edge cases you should test for to fix?
//what happens when point is at the start or the end of the array?
When splitting the array you are going to want to create two new arrays that will include what you are splitting, for example arr1 and arr2. To populate this arrays you are going to want to do something like this:
var arr1, arr2; // new arrays
int position = 0; // start position of second array
for(int i = 0; i <= arr.length(); i++){
if(arr[i] = point){ //when it finds the variable it stops adding to first array
//starts adding to second array
for(int j = i+1; j <= arr.length; j++){
arr2[position] = arr[j];
position++; //because we want to add from beginning of array i used this variable
}
break;
}
// add to first array
else{
arr1[i] = arr[i];
}
}
There are different ways to do this! good luck!
Yet another suggestion:
var segments = arr.join( '' ).split( point ).map(function( part ) {
return part.split( '' );
});
now segments contains an array of arrays:
[["a", "b"], ["d", "e", "f"]]
and can get accessed like
segments[ 0 ]; // ["a", "b"]
segments[ 1 ]; // ["d", "e", "f"]
if you want to split into equal half; why no simple while loop ?
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var c=[];
while(arr.length > c.length){
c.push(arr.splice(arr.length-1)[0]);
}
Kaboom :).
Separate two arrays with given array elements as string array and number array;
let arr = [21,'hh',33,'kk',55,66,8898,'rtrt'];
arrStrNum = (arr) => {
let str = [],num = [];
for(let i = 0;i<arr.length;i++){
if(typeof arr[i] == "string"){
str.push(arr[i]);
}else if(typeof arr[i] == "number"){
num.push(arr[i]);
}
}
return [str, num]
}
let ans = arrStrNum(arr);
let str = ans[0];
let num = ans[1];
console.log(str);
console.log(num);

Finding All Combinations (Cartesian product) of JavaScript array values

How can I produce all of the combinations of the values in N number of JavaScript arrays of variable lengths?
Let's say I have N number of JavaScript arrays, e.g.
var first = ['a', 'b', 'c', 'd'];
var second = ['e'];
var third = ['f', 'g', 'h', 'i', 'j'];
(Three arrays in this example, but its N number of arrays for the problem.)
And I want to output all the combinations of their values, to produce
aef
aeg
aeh
aei
aej
bef
beg
....
dej
EDIT: Here's the version I got working, using ffriend's accepted answer as the basis.
var allArrays = [['a', 'b'], ['c', 'z'], ['d', 'e', 'f']];
function allPossibleCases(arr) {
if (arr.length === 0) {
return [];
}
else if (arr.length ===1){
return arr[0];
}
else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var c in allCasesOfRest) {
for (var i = 0; i < arr[0].length; i++) {
result.push(arr[0][i] + allCasesOfRest[c]);
}
}
return result;
}
}
var results = allPossibleCases(allArrays);
//outputs ["acd", "bcd", "azd", "bzd", "ace", "bce", "aze", "bze", "acf", "bcf", "azf", "bzf"]
This is not permutations, see permutations definitions from Wikipedia.
But you can achieve this with recursion:
var allArrays = [
['a', 'b'],
['c'],
['d', 'e', 'f']
]
function allPossibleCases(arr) {
if (arr.length == 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push(arr[0][j] + allCasesOfRest[i]);
}
}
return result;
}
}
console.log(allPossibleCases(allArrays))
You can also make it with loops, but it will be a bit tricky and will require implementing your own analogue of stack.
I suggest a simple recursive generator function as follows:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
let remainder = tail.length ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log(...cartesian(first, second, third));
You don't need recursion, or heavily nested loops, or even to generate/store the whole array of permutations in memory.
Since the number of permutations is the product of the lengths of each of the arrays (call this numPerms), you can create a function getPermutation(n) that returns a unique permutation between index 0 and numPerms - 1 by calculating the indices it needs to retrieve its characters from, based on n.
How is this done? If you think of creating permutations on arrays each containing: [0, 1, 2, ... 9] it's very simple... the 245th permutation (n=245) is "245", rather intuitively, or:
arrayHundreds[Math.floor(n / 100) % 10]
+ arrayTens[Math.floor(n / 10) % 10]
+ arrayOnes[Math.floor(n / 1) % 10]
The complication in your problem is that array sizes differ. We can work around this by replacing the n/100, n/10, etc... with other divisors. We can easily pre-calculate an array of divisors for this purpose. In the above example, the divisor of 100 was equal to arrayTens.length * arrayOnes.length. Therefore we can calculate the divisor for a given array to be the product of the lengths of the remaining arrays. The very last array always has a divisor of 1. Also, instead of modding by 10, we mod by the length of the current array.
Example code is below:
var allArrays = [first, second, third, ...];
// Pre-calculate divisors
var divisors = [];
for (var i = allArrays.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * allArrays[i + 1].length : 1;
}
function getPermutation(n) {
var result = "", curArray;
for (var i = 0; i < allArrays.length; i++) {
curArray = allArrays[i];
result += curArray[Math.floor(n / divisors[i]) % curArray.length];
}
return result;
}
Provided answers looks too difficult for me. So my solution is:
var allArrays = new Array(['a', 'b'], ['c', 'z'], ['d', 'e', 'f']);
function getPermutation(array, prefix) {
prefix = prefix || '';
if (!array.length) {
return prefix;
}
var result = array[0].reduce(function(result, value) {
return result.concat(getPermutation(array.slice(1), prefix + value));
}, []);
return result;
}
console.log(getPermutation(allArrays));
You could take a single line approach by generating a cartesian product.
result = items.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
var items = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']],
result = items.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; }
Copy of le_m's Answer to take Array of Arrays directly:
function *combinations(arrOfArr) {
let [head, ...tail] = arrOfArr
let remainder = tail.length ? combinations(tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
Hope it saves someone's time.
You can use a typical backtracking:
function cartesianProductConcatenate(arr) {
var data = new Array(arr.length);
return (function* recursive(pos) {
if(pos === arr.length) yield data.join('');
else for(var i=0; i<arr[pos].length; ++i) {
data[pos] = arr[pos][i];
yield* recursive(pos+1);
}
})(0);
}
I used generator functions to avoid allocating all the results simultaneously, but if you want you can
[...cartesianProductConcatenate([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']])];
// ["acd","ace","acf","azd","aze","azf","bcd","bce","bcf","bzd","bze","bzf"]
Easiest way to find the Combinations
const arr1= [ 'a', 'b', 'c', 'd' ];
const arr2= [ '1', '2', '3' ];
const arr3= [ 'x', 'y', ];
const all = [arr1, arr2, arr3];
const output = all.reduce((acc, cu) => {
let ret = [];
acc.map(obj => {
cu.map(obj_1 => {
ret.push(obj + '-' + obj_1)
});
});
return ret;
})
console.log(output);
If you're looking for a flow-compatible function that can handle two dimensional arrays with any item type, you can use the function below.
const getUniqueCombinations = <T>(items : Array<Array<T>>, prepend : Array<T> = []) : Array<Array<T>> => {
if(!items || items.length === 0) return [prepend];
let out = [];
for(let i = 0; i < items[0].length; i++){
out = [...out, ...getUniqueCombinations(items.slice(1), [...prepend, items[0][i]])];
}
return out;
}
A visualisation of the operation:
in:
[
[Obj1, Obj2, Obj3],
[Obj4, Obj5],
[Obj6, Obj7]
]
out:
[
[Obj1, Obj4, Obj6 ],
[Obj1, Obj4, Obj7 ],
[Obj1, Obj5, Obj6 ],
[Obj1, Obj5, Obj7 ],
[Obj2, Obj4, Obj6 ],
[Obj2, Obj4, Obj7 ],
[Obj2, Obj5, Obj6 ],
[Obj2, Obj5, Obj7 ],
[Obj3, Obj4, Obj6 ],
[Obj3, Obj4, Obj7 ],
[Obj3, Obj5, Obj6 ],
[Obj3, Obj5, Obj7 ]
]
You could create a 2D array and reduce it. Then use flatMap to create combinations of strings in the accumulator array and the current array being iterated and concatenate them.
const data = [ ['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j'] ]
const output = data.reduce((acc, cur) => acc.flatMap(c => cur.map(n => c + n)) )
console.log(output)
2021 version of David Tang's great answer
Also inspired with Neil Mountford's answer
const getAllCombinations = (arraysToCombine) => {
const divisors = [];
let permsCount = 1;
for (let i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
permsCount *= (arraysToCombine[i].length || 1);
}
const getCombination = (n, arrays, divisors) => arrays.reduce((acc, arr, i) => {
acc.push(arr[Math.floor(n / divisors[i]) % arr.length]);
return acc;
}, []);
const combinations = [];
for (let i = 0; i < permsCount; i++) {
combinations.push(getCombination(i, arraysToCombine, divisors));
}
return combinations;
};
console.log(getAllCombinations([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']]));
Benchmarks: https://jsbench.me/gdkmxhm36d/1
Here's a version adapted from the above couple of answers, that produces the results in the order specified in the OP, and returns strings instead of arrays:
function *cartesianProduct(...arrays) {
if (!arrays.length) yield [];
else {
const [tail, ...head] = arrays.reverse();
const beginning = cartesianProduct(...head.reverse());
for (let b of beginning) for (let t of tail) yield b + t;
}
}
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log([...cartesianProduct(first, second, third)])
You could use this function too:
const result = (arrayOfArrays) => arrayOfArrays.reduce((t, i) => { let ac = []; for (const ti of t) { for (const ii of i) { ac.push(ti + '/' + ii) } } return ac })
result([['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']])
// which will output [ 'a/e/f', 'a/e/g', 'a/e/h','a/e/i','a/e/j','b/e/f','b/e/g','b/e/h','b/e/i','b/e/j','c/e/f','c/e/g','c/e/h','c/e/i','c/e/j','d/e/f','d/e/g','d/e/h','d/e/i','d/e/j']
Of course you can remove the + '/' in ac.push(ti + '/' + ii) to eliminate the slash from the final result. And you can replace those for (... of ...) with forEach functions (plus respective semicolon before return ac), whatever of those you are more comfortable with.
An array approach without recursion:
const combinations = [['1', '2', '3'], ['4', '5', '6'], ['7', '8']];
let outputCombinations = combinations[0]
combinations.slice(1).forEach(row => {
outputCombinations = outputCombinations.reduce((acc, existing) =>
acc.concat(row.map(item => existing + item))
, []);
});
console.log(outputCombinations);
let arr1 = [`a`, `b`, `c`];
let arr2 = [`p`, `q`, `r`];
let arr3 = [`x`, `y`, `z`];
let result = [];
arr1.forEach(e1 => {
arr2.forEach(e2 => {
arr3.forEach(e3 => {
result[result.length] = e1 + e2 + e3;
});
});
});
console.log(result);
/*
output:
[
'apx', 'apy', 'apz', 'aqx',
'aqy', 'aqz', 'arx', 'ary',
'arz', 'bpx', 'bpy', 'bpz',
'bqx', 'bqy', 'bqz', 'brx',
'bry', 'brz', 'cpx', 'cpy',
'cpz', 'cqx', 'cqy', 'cqz',
'crx', 'cry', 'crz'
]
*/
A solution without recursion, which also includes a function to retrieve a single combination by its id:
function getCombination(data, i) {
return data.map(group => {
let choice = group[i % group.length]
i = (i / group.length) | 0;
return choice;
});
}
function* combinations(data) {
let count = data.reduce((sum, {length}) => sum * length, 1);
for (let i = 0; i < count; i++) {
yield getCombination(data, i);
}
}
let data = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']];
for (let combination of combinations(data)) {
console.log(...combination);
}

Categories