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.