How to get all combination of the values from a 2D array's rows with JavaScript?

394 Views Asked by At

the 2D array I'm working with have different length for each row, something like:

var a = [2, 5, -12, 9];
var b = [54.0, 0.3];
var c = ["tree", "sun", "pool"]
var all = [a, b, c]

Any row in the 2D array might be zero sometimes. The array above is just an example.

What I want to do is to get one value from each row everyone, do something with these value, then get another combination of the values, etc.

Example:

//IF ALL ROWS HAVE CONTENT
var values = [all[0][0], all[1][0], all[2][0]];
//do something with it
values = [all[0][0], all[1][0], all[2][1]];
//do something with it
......
values = [all[0][3], all[1][1], all[2][2]];
//do something with it

//IF FIRST AND THRID ROWS HAVE CONTENT, THE SAMPLE OUTPUT
var values = [all[0][0], all[2][0]];
values = [all[0][0], all[2][1]];
......
values = [all[0][3], all[2][2]];

//IF ONLY SECOND ROWS HAVE CONTENT, THE SAMPLE OUTPUT
var values = [all[1][0]];
values = [all[1][1]];

Here are my thought on the logical flow of the codes

//count how many rows are not empty
var arrayCount = 0;
for(var i=0; i < all.length; i++){
   if(all[i].length !== 0){
      arrayCount++;
  }
}
//store the combination of values each time
var values = [];
//reference for rows
var x; var y;
//differentiate the looping based on the number of unempty rows
switch(arrayCount){
   //one unempty row
   case 1:
      //figure out which one is not empty and set a's pointer to it
      for(var q = 0; q < x.length; q++){
         values.push(x[q]);
         //do something with it
         values.splice(1, 0);
      }
      break;
   case 2:
      //figure out which one are not empty and set a and b's pointer to them (don't know how, maybe using two loops for each row?)
      for(var q = 0; q < x.length; q++){
         values.push(x[q]);
         for(var p = 0; p < y.length; p++){
            values.push(y[p]);
            //do something with it
            values.splice(1, 1);
         }
         values.splice(1, 0);
      }
      break;
   case 3:
      //set pointers to all the rows
      for(var q = 0; q < x.length; q++){
         values.push(x[q]);
         for(var p = 0; p < y.length; p++){
            values.push(y[p]);
            for(var r = 0; r < z.length; r++){
               values.push(z[r]);
               //do something with it
               values.splice(1, 2);
            }
            values.splice(1, 1);
         }
         values.splice(1, 0);
      }
      break;
}

I'm afraid the whole code is too long, and having some duplicate codes in the switch. Is that possible to simplify it?

I did saw a post with same question, and I tried its answer. Unfortunely, the platform I'm coding on (Fandom) doesn't support this generator function. I asked, it's only support Javascript upto ES3 or ES4.

Thank you for taking a look at this question!

3

There are 3 best solutions below

3
On BEST ANSWER

Here's a solution that handles empty arrays and does not use generator functions.

var combinations = all.reduce(function (previous, current) {

    if (current.length === 0)
        return previous;

    if (previous.length === 0)
        return current;

    const accumulate = current.map(function (x){

        return previous.map(function(y) {

            // Make one array if the accumulated result is an array
            if (y.length > 0)
                return y.concat(x);

            return [x, y];
        });
    });

    // Flatten combinations
    return accumulate.reduce( function (acc, x) {
        return acc.concat(x);
    });
});
3
On

I ran this answer to a similar question through babel's online repl and got ugly but operational code that appears to do what you want.

Caveat: This references Symbol.iterator, which may not be available in ES4. I haven't researched it. The babel link above includes the original solution and this transpilation if you want to tinker with the babel settings for compatibility.

I didn't post this initially because it looked like people had already answered your question using the same algorithm without the ugliness of transpiling, but since you asked...

Here's the result running against your sample input:

const a = [2, 5, -12, 9];
const b = [54.0, 0.3];
const c = ["tree", "sun", "pool"];
const all = [a, b, c];

function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }

function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }

function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }

function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }

var makeCartesian = function makeCartesian() {
  var t = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  return function (a) {
    for (var _len = arguments.length, more = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      more[_key - 1] = arguments[_key];
    }

    return a === undefined ? [t] : a.flatMap(function (x) {
      return makeCartesian([].concat(_toConsumableArray(t), [x])).apply(void 0, more);
    });
  };
};

var cartesian = makeCartesian();
console.log(cartesian.apply(void 0, _toConsumableArray(all)));

2
On

You could take a recursive approach by taking the array of arrays and iterate each inner array and hand over an array of the collected items until no more arrays are available.

function getCartesian(array) {
    function iter(temp)  {
        var i = temp.length, j;
        if (i >= array.length) {
            return result.push(temp);
        }
        for (j = 0; j < array[i].length; j++) {
            iter(temp.concat(array[i][j]));
        }
    }

    var result = [];
    iter([]);
    return result;
}

console.log(getCartesian([[2, 5, -12, 9], [54.0, 0.3], ["tree", "sun", "pool"]]).map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

With one function

function getCartesian(array) {
    var i, j,
        first = array.shift(),
        temp = [],
        result = [];

    if (!first) return;
    if (!array.length) return first;
    temp = getCartesian(array);

    for (i = 0; i < first.length; i++)
        for (j = 0; j < temp.length; j++)
            result.push([first[i]].concat(temp[j]));

    return result;
}

console.log(getCartesian([[2, 5, -12, 9], [54.0, 0.3], ["tree", "sun", "pool"]]).map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }