如何在 Javascript 中生成来自多个对象的项目的所有组合

How to generate in Javascript all combinations of items from multiple objects

我有一组带有名称和选项的对象,我需要所有可能的产品组合。重要的是这个数组有 N 个对象,每个对象都有 N 个选项。

我试图创建某种递归算法,但问题是我最终未能递归推送以接收所需的数据结构。我也尝试了 Cartesian product of multiple arrays in JavaScript 的方法,但它似乎与所需的输出无关。

示例:

input = [
    {
        name: "Size",
        options: [ { value: "S" }, { value: "M" }, { value: "L" }, ...and so on]
    },
    {
        name: "Color",
        options: [ { value: "Red" }, { value: "White" }, { value: "Blue" }, ...and so on]
    },
    {
        name: "Weight",
        options: [ { value: "1kg" }, { value: "2kg" }, { value: "3kg" }, { value: "4kg"}, ]
    },
    .... and so on
];

我需要以数组的形式获得所有可能的组合,该数组本身包含一个对象数组以及对象的名称和值。

示例(数组的数组):

    output = [ 
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '1kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '2kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '3kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '4kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '1kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '2kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '3kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '4kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Blue'}, {name: 'Weight', value: '1kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Blue'}, {name: 'Weight', value: '2kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Blue'}, {name: 'Weight', value: '3kg'} ],
    [ {name: 'Size', value: 'S'}, {name: 'Color', value: 'Blue'}, {name: 'Weight', value: '4kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '1kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '2kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '3kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'Red'}, {name: 'Weight', value: '4kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '1kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '2kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '3kg'} ],
    [ {name: 'Size', value: 'M'}, {name: 'Color', value: 'White'}, {name: 'Weight', value: '4kg'} ],
    ... and so on 
];

更新:根据 Mulan 的建议修复了第一个 cartesian 实现。

这绝对是一道笛卡尔积题。唯一的问题是您需要在调用笛卡尔积之前格式化您的输入。这是一个版本,使用一个简单的递归 cartesian 函数。

const cartesian = ([xs, ...xss]) =>
  xs == undefined
    ? [[]]
  : xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))

const combine = (properties) =>
  cartesian (properties .map (({name, options}) => options .map (({value}) => ({name, value}))))

const properties = [{name: "Size", options: [ { value: "S" }, { value: "M" }, { value: "L" }, /*...and so on */]}, {name: "Color", options: [ { value: "Red" }, { value: "White" }, { value: "Blue" }, /*...and so on */]}, {name: "Weight", options: [ { value: "1kg" }, { value: "2kg" }, { value: "3kg" }, { value: "4kg"}, ]}, /* .... and so on */]

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

但我发现您的输出格式非常重复。我更喜欢这样的东西:

[
  {Size: "S", Color: "Red", Weight: "1kg"},
  {Size: "S", Color: "Red", Weight: "2kg"},
  {Size: "S", Color: "Red", Weight: "3kg"},
  {Size: "S", Color: "Red", Weight: "4kg"},
  {Size: "S", Color: "White", Weight: "1kg"},
  // ...
  {Size: "L", Color: "Blue", Weight: "4kg"},
] 

我们可以通过更多的工作来实现它:

const cartesian = ([xs, ...xss]) =>
  xs == undefined
    ? [[]]
  : xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))


const combine = (properties) =>
  cartesian (properties .map (
    ({name, options}) => options .map (({value}) => ({[name]: value}))
  )) .map (ps => Object .assign ({}, ...ps))

const properties = [{name: "Size", options: [ { value: "S" }, { value: "M" }, { value: "L" }, /*...and so on */]}, {name: "Color", options: [ { value: "Red" }, { value: "White" }, { value: "Blue" }, /*...and so on */]}, {name: "Weight", options: [ { value: "1kg" }, { value: "2kg" }, { value: "3kg" }, { value: "4kg"}, ]}, /* .... and so on */]

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

如果那个版本的笛卡尔积对你来说没有意义,这个是替代方案:

const cartesian = ([x, ...xs]) => 
  (xs || []) .reduce (
    (a, b) => a .reduce (
      (c, d) => [... c, ... (b .map (e => [... d, e]))],
      []
    ),
    (x || []) .map (x => [x])
  )

这只是对@Scott 已经很棒的回答的补充。我想为 cartesian -

提供两种选择
const cartesian = ([t, ...more]) =>
  t == null
    ? [[]]
    : t.flatMap(v => cartesian(more).map(r => [v, ...r]))

使用 Scott 的 combine 函数 -

for (const c of combine(properties))
  console.log(c)
{"Size":"S","Color":"Red","Weight":"1kg"}
{"Size":"S","Color":"Red","Weight":"2kg"}
{"Size":"S","Color":"Red","Weight":"3kg"}
{"Size":"S","Color":"Red","Weight":"4kg"}
{"Size":"S","Color":"White","Weight":"1kg"}
{"Size":"S","Color":"White","Weight":"2kg"}
{"Size":"S","Color":"White","Weight":"3kg"}
{"Size":"S","Color":"White","Weight":"4kg"}
{"Size":"S","Color":"Blue","Weight":"1kg"}
{"Size":"S","Color":"Blue","Weight":"2kg"}
{"Size":"S","Color":"Blue","Weight":"3kg"}
{"Size":"S","Color":"Blue","Weight":"4kg"}
{"Size":"M","Color":"Red","Weight":"1kg"}
{"Size":"M","Color":"Red","Weight":"2kg"}
{"Size":"M","Color":"Red","Weight":"3kg"}
{"Size":"M","Color":"Red","Weight":"4kg"}
{"Size":"M","Color":"White","Weight":"1kg"}
{"Size":"M","Color":"White","Weight":"2kg"}
{"Size":"M","Color":"White","Weight":"3kg"}
{"Size":"M","Color":"White","Weight":"4kg"}
{"Size":"M","Color":"Blue","Weight":"1kg"}
{"Size":"M","Color":"Blue","Weight":"2kg"}
{"Size":"M","Color":"Blue","Weight":"3kg"}
{"Size":"M","Color":"Blue","Weight":"4kg"}
{"Size":"L","Color":"Red","Weight":"1kg"}
{"Size":"L","Color":"Red","Weight":"2kg"}
{"Size":"L","Color":"Red","Weight":"3kg"}
{"Size":"L","Color":"Red","Weight":"4kg"}
{"Size":"L","Color":"White","Weight":"1kg"}
{"Size":"L","Color":"White","Weight":"2kg"}
{"Size":"L","Color":"White","Weight":"3kg"}
{"Size":"L","Color":"White","Weight":"4kg"}
{"Size":"L","Color":"Blue","Weight":"1kg"}
{"Size":"L","Color":"Blue","Weight":"2kg"}
{"Size":"L","Color":"Blue","Weight":"3kg"}
{"Size":"L","Color":"Blue","Weight":"4kg"}

cartesian 的另一种方法可能如下所示。生成器总是非常适合涉及组合和排列的问题 -

function* cartesian([t, ...more]) {
  if (t == null)
    return yield []
  for (const v of t)
    for (const c of cartesian(more))
      yield [v, ...c]
}

function* combine(t) {
  for (const ps of cartesian(t.map(({name, options}) => options.map(({value}) => ({[name]: value})))))
    yield Object.assign({}, ...ps)
}
for (const c of combine(properties))
  console.log(c)

输出相同 -

{"Size":"S","Color":"Red","Weight":"1kg"}
{"Size":"S","Color":"Red","Weight":"2kg"}
{"Size":"S","Color":"Red","Weight":"3kg"}
...
{"Size":"L","Color":"Blue","Weight":"2kg"}
{"Size":"L","Color":"Blue","Weight":"3kg"}
{"Size":"L","Color":"Blue","Weight":"4kg"}

如果您想将结果收集到数组中,请使用 Array.from