This is a functional ES6 approach.
let variants = [{
variantName: "Size",
variantItems: [
"XL",
"MD",
"SM"
]
},
{
variantName: "Color",
variantItems: [
"Red",
"Blue"
]
}];
let crossJoined = new Array(variants.reduce((product, variant) => (product * variant.variantItems.length), 1))
.fill(0)
.reduce(crossJoin => {
crossJoin.data.push(crossJoin.currentIndexes.map((itemIndex, variantIndex) => `${variants[variantIndex].variantName}: ${variants[variantIndex].variantItems[itemIndex]}`).join(", "));
let incrementableIndex = variants.length - crossJoin.currentIndexes
.slice()
.reverse()
.findIndex((itemIndex, variantIndex) => variants[variants.length - variantIndex - 1].variantItems.length > itemIndex + 1) - 1;
crossJoin.currentIndexes[incrementableIndex]++;
crossJoin.currentIndexes = crossJoin.currentIndexes.map((value, index) => (index > incrementableIndex
? 0
: value));
return (crossJoin.currentIndexes.length == variants.length
? crossJoin
: crossJoin.data);
}, {
data: [],
currentIndexes: new Array(variants.length).fill(0)
}).join("\n");
console.log(crossJoined);
First, an array is created with a length of all variantItems
array lengths multiplied, then it is zero-filled. Next, we reduce
it to another array.
crossJoin
is the aggregator that holds an object of this structure most of the time:
{
data: [ … ],
currentIndexes: [ 1, 3 ] // Corresponds to variants[0].variantItems[1]
// and variants[1].variantItems[3]
}
That is “most of the time”, until the end, where currentIndexes
won’t be used anymore and only its data
property is returned.
So in each reduce
iteration, we first push a new entry to crossJoin.data
, using crossJoin.currentIndexes
and the indexes of that array. Removing .join(", ")
will result in an entry of the structure
[ "Size", "XL" ]
Next, we need to increment the numbers in crossJoin.currentIndexes
:
[ 0, 0 ]
should be incremented to [ 0, 1 ]
, because there is a second color at the index 1
. [ 0, 1 ]
should be incremented to [ 1, 0 ]
, because there is no third color, but a second size, but then we need the first color again, and so on. The last valid index array is [ 2, 1 ]
which corresponds to the third size and the second color, which is the last combination.
incrementableIndex
is the last possible index that can still be incremented. Once incremented, all subsequent indexes have to be 0
.
You see those variants.length -
something - 1
twice, because you need to find the first index from the end, so you have to reverse
(a copy of — hence the slice
) the array, then re-interpret the found index as an index from the start again.
The return
returns this crossJoin
object for the next iteration. The condition crossJoin.currentIndexes.length == variants.length
applies to the very end, where no index can be incremented anymore. It adds a NaN
to the currentIndexes
array, so the lengths don’t match, and instead of filtering out or preventing the NaN
, I just ignored and discarded it altogether.
The output will be an array of all combinations. You can .join("\n")
it to make a string where each combination is separated by a line-break, or you can use .forEach
to append each combination to a list, for example.
0
solved array cross-over loop in javascript