Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create all combinations of this object's keys/values in JavaScript?

I have the following JavaScript object structure:

var options = {
    optionOne: [true, false],
    optionTwo: [true, false],
    optionThree: [
        null,
        {property1: 9, property2: 7},
        {property1: 4, property2: 12},
        {property1: 16, property2: 14}
    ]
};

Please note that the number of key/pairs in this object will differ. So there might actually be optionFour, optionFive, etc., and each option can have any number or type of values for its array.

I need to iterate through this object and create an array containing objects of all possible option combinations:

[
    {optionOne: true,  optionTwo, true,  optionThree: null},
    {optionOne: false, optionTwo, true,  optionThree: null},
    {optionOne: true,  optionTwo, false, optionThree: null},
    {optionOne: false, optionTwo, false, optionThree: null},
    {optionOne: true,  optionTwo, true,  optionThree: {property1: 9, property2: 7}},
    {optionOne: false, optionTwo, true,  optionThree: {property1: 9, property2: 7}},
    {optionOne: true,  optionTwo, false, optionThree: {property1: 9, property2: 7}},
    {optionOne: false, optionTwo, false, optionThree: {property1: 9, property2: 7}},
    {optionOne: true,  optionTwo, true,  optionThree: {property1: 4, property2: 12}},
    {optionOne: false, optionTwo, true,  optionThree: {property1: 4, property2: 12}},
    {optionOne: true,  optionTwo, false, optionThree: {property1: 4, property2: 12}},
    {optionOne: false, optionTwo, false, optionThree: {property1: 4, property2: 12}},
    {optionOne: true,  optionTwo, true,  optionThree: {property1: 16, property2: 14}},
    {optionOne: false, optionTwo, true,  optionThree: {property1: 16, property2: 14}},
    {optionOne: true,  optionTwo, false, optionThree: {property1: 16, property2: 14}},
    {optionOne: false, optionTwo, false, optionThree: {property1: 16, property2: 14}}
]

I'm struggling on how to accomplish this but am fairly confident the answer lies in recursion.

Can the algorithm gods help me?

like image 583
Chad Johnson Avatar asked Sep 29 '15 07:09

Chad Johnson


2 Answers

function getCombinations(options, optionIndex, results, current) {
    var allKeys = Object.keys(options);
    var optionKey = allKeys[optionIndex];

    var vals = options[optionKey];

    for (var i = 0; i < vals.length; i++) {
        current[optionKey] = vals[i];

        if (optionIndex + 1 < allKeys.length) {
            getCombinations(options, optionIndex + 1, results, current);
        } else {
            // The easiest way to clone an object.
            var res = JSON.parse(JSON.stringify(current));
            results.push(res);
        }
    }

    return results;
}

Use it like this:

var results = getCombinations(options, 0, [], {});

Here's a working JSFiddle example.

like image 85
Dmytro Shevchenko Avatar answered Nov 02 '22 23:11

Dmytro Shevchenko


This was recently resurrected, and I think modern JS offers a cleaner way to write this.

const crossproduct = (xss) => 
  xss.reduce((xs, ys) => xs.flatMap(x => ys.map(y => [...x, y])), [[]])

const combinations = (o, keys = Object .keys (o), vals = Object .values (o)) =>
  crossproduct(vals).map(xs => Object.fromEntries(xs.map ((x, i) => [keys[i], x])))


const options = {optionOne: [true, false], optionTwo: [true, false], optionThree: [null, {property1: 9, property2: 7}, {property1: 4, property2: 12}, {property1: 16, property2: 14}]}

console .log (JSON .stringify (
  combinations (options)
, null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}

We start with a crossproduct function which, for instance, takes

[[1, 2], ['a', 'b', 'c'], ['T', 'F']]

and returns

[
  [1, 'a', 'T'], [1, 'a', 'F'], [1, 'b', 'T'], [1, 'b', 'F'], [1, 'c', 'T'], [1, 'c', 'F'], 
  [2, 'a', 'T'], [2, 'a', 'F'], [2, 'b', 'T'], [2, 'b', 'F'], [2, 'c', 'T'], [2, 'c', 'F']
]

Then combinations takes our object apart with Object.keys, and Object.values, passes the values to crossproduct, then for each array in the result, maps over the values, associating the corresponding key with each, then rehydrating an object with Object.fromEntries.

This order is what seems like the logical order of results to me. But if we swapped out the returned expression for crossproduct with the following, we would get the order mentioned in the question:

  xss .reduce ((xs, ys) => ys .flatMap (y => xs .map (x => [...x, y])), [[]])
like image 33
Scott Sauyet Avatar answered Nov 03 '22 01:11

Scott Sauyet