Search code examples
javascriptarraysenumeratecartesianpowerset

Create cartesian product (powerset?) of JavaScript objects by looping through unknown number of arrays


I'm a beginner, so please pardon my ignorance if this is trivial.

I have a javascript object of unknown length, and each property's value is an array (also of unknown length to me). For example:

var obj = {"varA":[1,2,3],
           "varB":['good','bad'],
           "varC":[0,100],
           "varD":['low','med','high']
          }

I want to loop through each of the properties and create a new object for every combination of property values. If I knew the number of properties I could just brute-forcedly use for loops, but is there a way to enumerate without knowing how many loops to hard-code?

I essentially want to do this kind of thing:

var oblist = [];
for (a in varA){
 for (b in varB){
  for (c in varC){
   for (d in varD){
    oblist.push({"varA":varA[a], "varB":varB[b], "varC":varC[c], "varD":varD[d]});
   }
  }
 }
}

so that oblist will contain objects like:

{"varA":1, "varB":"good", "varC":0, "varD":"low"}
{"varA":1, "varB":"good", "varC":0, "varD":"med"}
...
{"varA":3, "varB":"bad", "varC":100, "varD":"high"}

Thanks!

Edit: Look I'm not asking for for-loop or indexing syntax help. I'm asking what to do if I don't know the number of properties in the object (e.g. varA, varB, varC, varD, varE, hell i could have varZZ for all i know), so I can't just hard-code 4 for loops. Is there a way to set that using obj[Object.keys(obj)[i]].length?


Solution

  • var obj = {"varA":[1,2,3],
               "varB":['good','bad'],
               "varC":[0,100],
               "varD":['low','med','high']
              }
     
    // flatten the object into an array so it's easier to work with
    var obj2list = function(obj) {
      var list = [];
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          list.push({
            name: key,
            val: obj[key]
          });
        }
      }
      return list;
    };
     
    // implement your favorite version of clone...this isn't particular fast
    var cloneObj = function(obj) {
      return JSON.parse(JSON.stringify(obj));
    }
     
    var iterateAndPopulateCombo = function(currentObj, listToIterate, result) {
      if (listToIterate.length == 0) {
        result.push(currentObj);
      } else {
        listToIterate[0].val.forEach(function(d) {
          var newObj = cloneObj(currentObj);
          newObj[listToIterate[0].name] = d;
          iterateAndPopulateCombo(newObj, listToIterate.slice(1), result);
        })
      }
    }
     
    var list = obj2list(obj);
    var result = [];
    iterateAndPopulateCombo({}, list, result);
    console.log(JSON.stringify(result));
    document.body.appendChild(document.createTextNode(JSON.stringify(result)));