2

I have found many solutions about all posible combinations between array values but I need something different, I hope you could support me. Basically is to create all posible objects that combine array keys with true|false values, something like this:

Input: (Should return an array of 32 objects, 2exp5, two posible values in 5 keys)

let properties = ['arm','lens','season','food','size'];

Output:

let combinations = [
  {"arm": "false","lens": "false","season": "false","food": "false","size": "false"}
  {"arm": "false","lens": "false","season": "false","food": "false","size": "true"}
  {"arm": "false","lens": "false","season": "false","food": "true","size": "true"}
  {"arm": "false","lens": "false","season": "true","food": "true","size": "true"}
  {"arm": "false","lens": "true","season": "true","food": "true","size": "true"}
  {"arm": "true","lens": "true","season": "true","food": "true","size": "true"}
  {"arm": "true","lens": "true","season": "true","food": "false","size": "true"}
  {"arm": "true","lens": "true","season": "false","food": "false","size": "true"}
  and so on...
]

Thank you so much!

Siva K V
  • 10,561
  • 2
  • 16
  • 29
eCoder
  • 63
  • 6

3 Answers3

1

You could use a 2D matrix with on and off switches for each property. Then create the entries for each key and create the object using Object.fromEntries()

0 0 0 0 0
0 0 0 0 1
0 0 0 1 0
0 0 0 1 1
etc
  • You need a total of 2 ** keys.length objects in the array. So, create that using Array.from({ 2 ** keys.length })

  • In the map function, create a binary number for the current row using row.toString(2)

  • Add the leading 0s until the string is keys.length long: ("00001")

  • split the string to make it an array (["0", "0", "0", "0", "1"])

  • map this array and create an array of entries for the corresponding key

    [["arm","false"],["lens","false"],["season","false"],["food","false"],["size","false"]]

  • Create an object from the array of entries using Object.fromEntries()

Here's a snippet:

let keys = ['arm', 'lens', 'season', 'food', 'size'];

function combination(keys) {
  return Array.from({ length: 2 ** keys.length }, (_, row) =>
    Object.fromEntries(
      row.toString(2)
          .padStart(keys.length, 0)
          .split('')
          .map((binary, j) => [keys[j], String(Boolean(+binary))])
    )
  )
}

console.log(combination(keys))
Community
  • 1
  • 1
adiga
  • 34,372
  • 9
  • 61
  • 83
  • You nailed it bro! Could you recommend me any topic or course to learn in order to be able to solve these kind of problems on my own? I am used to conventional front requesting data from ws, but I'd like to improve my solving logic problems skills. Thank you very much! – eCoder Jun 01 '20 at 01:07
1

We can build this on two reusable functions, like this:

const crossproduct = (xss) =>
  xss.reduce ((ps, xs) => ps.reduce ((r, p) => [...r, ...(xs.map (x => [...p, x]))], []), [[]])

const mergeAll = xs => 
  Object.assign ({}, ...xs)

const combine = (properties, values) => 
  crossproduct (properties.map (p => values.map (v => ({[p]: v}))))
    .map (mergeAll)


const properties = ['arm', 'lens', 'season', 'food', 'size']
const values = [true, false]

console.log (combine (properties, values))
.as-console-wrapper {min-height: 100% !important; top: 0}

Our utility functions are:

  • crossproduct, which takes an array of arrays and finds the cartesian product of them. For instance,

    crossproduct ([['a', 'b', 'c'], [1, 2], ['x', 'y']])
      //=> [["a", 1, "x"], ["a", 1, "y"], ["a", 2, "x"], ["a", 2, "y"], 
      //    ["b", 1, "x"], ["b", 1, "y"], ["b", 2, "x"], ["b", 2, "y"], 
      //    ["c", 1, "x"], ["c", 1, "y"], ["c", 2, "x"], ["c", 2, "y"]]
    
  • and mergeAll, which combines an array of separate objects into one like this:

    mergeAll ([{foo: 1}, {bar: 2}, {baz: 3}])
      //=> {foo: 1, bar: 2, baz: 3}
    

    This is just a thin wrapper around Object.assign, simply applying it to an array rather than individual objects.

Our main function, combine first creates an array of arrays matching individual property names to values like this:

  [
    [{"arm": true}, {"arm": false}], 
    [{"lens": true}, {"lens": false}], 
    [{"season": true}, {"season": false}], 
    [{"food": true}, {"food": false}], 
    [{"size": true}, {"size": false}]
]

This is the bit properties.map (p => values.map (v => ({[p]: v}))). While that could be extracted as a stand-alone function, it doesn't seem to have any other uses, so the decision is simply a matter of code aesthetics.

We call crossproduct on that result, getting this intermediate format:

[
  [{"arm": true}, {"lens": true}, {"season": true}, {"food": true}, {"size": true}], 
  [{"arm": true}, {"lens": true}, {"season": true}, {"food": true}, {"size": false}], 
  [{"arm": true}, {"lens": true}, {"season": true}, {"food": false}, {"size": true}], 
  [{"arm": true}, {"lens": true}, {"season": true}, {"food": false}, {"size": false}],
  // ... 
  [{"arm": false}, {"lens": false}, {"season": false}, {"food": false}, {"size": false}]
]

And finally, we call .map (mergeAll) on that array to get our final format.

Note that if you really have no other use for mergeAll, it's short and can easily be inlined in the main function as .map (xs => Object.assign ({}, ...xs)). It's a function that I use often and would simply have in handy in my utility toolbox, so I personally wouldn't inline it. You may feel differently.


Do notice the basic idea here. We don't try to solve the problem in one go, but rather apply a series of transforms to get to our final format. This allows us to take advantage of reusable functions for some of those steps. It's a powerful technique.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Haven't crossed paths in a bit. Delightful decomposition results in fully reusable parts. This reminds me a lot of the steps required to build a [truth table](https://en.wikipedia.org/wiki/Truth_table). I have a module sitting around maybe I'll add that one as an answer for fun – Mulan Jun 01 '20 at 22:19
  • Very nice answer and justification, thank you very much! – eCoder Jun 02 '20 at 02:55
0

Start building from empty array and add the 2 possibilities for key (true/false). Repeat the process by traversing all keys, for each key take the available results from previous.

let properties = ["arm", "lens", "season", "food", "size"];

const addTwo = (arr, key) => {
  const result = [];
  ["true", "false"].forEach((val) =>
    arr.forEach((item) => result.push({ ...item, [key]: val }))
  );
  return result;
};

const combinations = (arr) => {
  let output = [{}];
  arr.forEach((key) => (output = addTwo(output, key)));
  return output;
};

console.log(combinations(properties));
Siva K V
  • 10,561
  • 2
  • 16
  • 29