[Solved] array cross-over loop in javascript


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