5

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?

  • 1
    Please show what you want the final output data structure to look like for the `obj` input you have specified. I'm not following what you want. – jfriend00 Jan 09 '15 at 22:47
  • Did you mean you want the [`power set`](http://en.wikipedia.org/wiki/Power_set) of each array in the object? If so then this is not just a duplicate of object access (@FelixKing). – Xotic750 Jan 09 '15 at 23:26
  • 4
    Hi Felix, with due respect for your rep and the success of the answer you've linked to, may I say I don't think this is a dupe? To answer this question you'll need Object.keys, which your linked answer doesn't discuss, and some logic to pull it all together to get the combos the OP is after. While people often get criticised on SO for asking dupe Qs, or being too broad, actually I think there's a poorly-described but specific challenge here, which your excellent broad answer doesn't actually cover. – sifriday Jan 09 '15 at 23:35
  • Xotic, I read it not as the power set, but as all the 4-item arrays from [1, good, 0, low], [1, good, 0, med] ... [3, bad, 100, high] ... and in each array the items are actually objs like [varA:1, varB:good, varC:0, varD:low]. What do you think? – sifriday Jan 09 '15 at 23:39
  • This `every combination of property values` is what make me suspect the power set. But unless the OP replies then we will never know and this question will never be reopened. – Xotic750 Jan 09 '15 at 23:40
  • @user4438610 I would update your question with your expected output given the specific `obj` that you have given. – Xotic750 Jan 09 '15 at 23:45
  • Also thanks @FelixKling but I don't believe this is a duplicate of your question – user4438610 Jan 09 '15 at 23:48
  • 1
    If you can improve your question by adding detail to show that it is not a duplicate of the other answer then we can work to get this reopened. – Xotic750 Jan 09 '15 at 23:51
  • Yes @sifriday that's exactly what I meant! – user4438610 Jan 09 '15 at 23:51
  • Since I can't post answers anymore, there's a gist to what you want: https://gist.github.com/hankduan/87fb07561c2caee27d0f – hankduan Jan 09 '15 at 23:52
  • @user4438610 does the solution that hankduan provided give you the results that you are expecting? – Xotic750 Jan 10 '15 at 00:00
  • The original question is clear (he does show example output) and not a dup of the question Felix cited. I'm voting to reopen. – user949300 Jan 10 '15 at 00:01
  • I'm not sure why so many thought it's unclear. I felt it was clear even before he added the examples. – hankduan Jan 10 '15 at 00:02
  • There is a section in the duplicate question that says: *What if the property names are dynamic and I don't know them beforehand?* . Did you read that? – Felix Kling Jan 10 '15 at 00:04
  • @FelixKling I think user4438610 intended for this to be more of an algorithm question than a syntax question – hankduan Jan 10 '15 at 00:09
  • @Felix, your answer is great for accessing a single property. And hints at accessing more. But it's like teaching somebody to juggle a single item, then when they ask how to juggle N items, you say "sorry, already answered". And, IMO, it's an algorithm question for a power set type of solution too, not at all discussed in your answer. So it's really more like teaching somebody to fly an airplane in a stright line for 1 minute, then telling them "now tyou are ready to fly around the world". – user949300 Jan 10 '15 at 00:09
  • I don't feel that is the be all end all of this question, it looks very much like a recursive/power set solution and should probably have been tagged as `algorithm` or similar. It has my vote for reopen. – Xotic750 Jan 10 '15 at 00:09
  • 1
    @hankduan: All of the points made in the question seem to indicate that the OP doesn't know how to iterate over an object. However, I'm OK with reopening it. Someone should provide a better title though. – Felix Kling Jan 10 '15 at 00:11
  • I think the combos required are the cartesian product of all the inner arrays. Demo here - not quite complete. http://jsfiddle.net/sifriday/qmyxhhny/1/ I'll suggest cartesian in a title edit, but I haven't got enough points to make the official (not a moderator). – sifriday Jan 10 '15 at 00:17
  • Other examples of cartesian product using javascript http://stackoverflow.com/search?q=%5Bjavascript%5D+cartesian+product – Xotic750 Jan 10 '15 at 00:39
  • For the mods, re powerset/cartesian product query in the title, I'm pretty sure its not the power set, because that would include 2-item results like {varA:1, varB:good} as well as all the 4-item combos. – sifriday Jan 10 '15 at 00:50

2 Answers2

1

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)));
Xotic750
  • 22,914
  • 8
  • 57
  • 79
hankduan
  • 5,994
  • 1
  • 29
  • 43
  • Nice. Just while I was in jsfiddle doing my own version, I made a fiddle for yours to try it out - I think we have the same result, so that's the problem understood anyway! Here's yours: http://jsfiddle.net/sifriday/udubdv5j/ – sifriday Jan 10 '15 at 00:29
  • Yes, your results match. :) – Xotic750 Jan 10 '15 at 00:33
1

The combos you need are the cartesian product of all the arrays within your obj, here's a fiddle showing it in action:

http://jsfiddle.net/sifriday/qmyxhhny/2/

And the code...

// I think the combo you're after is known as cartesian product
// Here's a function to do it, from:
// http://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript
// It needs Underscore.js
function cartesianProductOf() {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
};

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

// Now I extract the arrays from your object
var idx, jdx, keys = Object.keys(obj), arrays = [], result1 = [], result2 = []
for (idx in keys) {
    var key = keys[idx]
    var arr = obj[key]
    arrays.push(arr)
}

// We can calculate the combos of the obj, but this isn't annotated. 
result1 = cartesianProductOf.apply(null, arrays)

// Now turn these back into annotated objects.
for (idx in result1) {
    var tmp = result1[idx], obj = {}
    for (jdx in tmp) {
       obj[keys[jdx]] = tmp[jdx]
    }
    result2.push(obj)
}

// Done!
console.log(result2)

With a bit of effort I think this could be tidied up; you could probably ensure the annotation happens within the cartesian product.

Xotic750
  • 22,914
  • 8
  • 57
  • 79
sifriday
  • 4,342
  • 1
  • 13
  • 24
  • Does the link that I added for cartesian product match your thinking? If it does and you have a better title I believe that I can edit that. – Xotic750 Jan 10 '15 at 00:35
  • thanks for adding that link - I was just reading it! I think so. I think strictly it is covered in the section on n-ary Cartesian product, it being the product of 4 sets. – sifriday Jan 10 '15 at 00:39
  • (it was your comment about power sets got me thinking about approaching this from set theory) – sifriday Jan 10 '15 at 00:40
  • I voted on your title change but 2 others disagree on the change. I think we should add `cartesian product` as a tag. – Xotic750 Jan 10 '15 at 00:40
  • ahh yep I see they've gone for powerset. I'm pretty sure it's not that, because a powerset would include 2-item results like {varA:1, varB:good} as well as all the 4-item combos? – sifriday Jan 10 '15 at 00:48