我有以下 JavaScript 对象结构:
var options = {
optionOne: [true, false],
optionTwo: [true, false],
optionThree: [
null,
{property1: 9, property2: 7},
{property1: 4, property2: 12},
{property1: 16, property2: 14}
]
};
请注意,该对象中的密钥/对的数量会有所不同。因此,实际上可能有
optionFour
、optionFive
等,并且每个选项的数组可以有任意数量或类型的值。
我需要迭代这个对象并创建一个包含所有可能选项组合的对象的数组:
[
{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}}
]
我正在努力解决如何实现这一目标,但我相当有信心答案在于递归。
算法大神能帮帮我吗?
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;
}
像这样使用它:
var results = getCombinations(options, 0, [], {});
这是一个有效的 JSFiddle 示例。
这最近又复活了,我认为现代 JS 提供了一种更简洁的方式来编写它。
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}
我们从一个
crossproduct
函数开始,例如,需要
[[1, 2], ['a', 'b', 'c'], ['T', 'F']]
并返回
[
[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']
]
然后
combinations
将我们的对象与 Object.keys
分开,然后 Object.values
将值传递给 crossproduct
,然后对于结果中的每个数组,映射这些值,将相应的键与每个值相关联,然后重新水合带有 Object.fromEntries
的对象。
这个顺序对我来说似乎是结果的逻辑顺序。 但是,如果我们用以下内容替换
crossproduct
返回的表达式,我们将得到问题中提到的顺序:
xss .reduce ((xs, ys) => ys .flatMap (y => xs .map (x => [...x, y])), [[]])
这是基于 Dmytro 的answer的改进:
function getPermutations(object, index = 0, current = {}, results = []) {
const keys = Object.keys(object);
const key = keys[index];
const values = object[key];
for (const value of values) {
current[key] = value;
const nextIndex = index + 1;
if (nextIndex < keys.length) {
this.getPermutations(object, nextIndex, current, results);
} else {
const result = Object.assign({}, current);
results.push(result);
}
}
return results;
}
改进:
const p = getPermutations(object);
对于任何寻求基于 TypeScript 答案的人:
export function getCartesian<TOut extends Record<string, unknown[]>>(inputObject: TOut): Array<{ [K in keyof TOut]: TOut[K][number] }> {
// Reduce the entries of the input object to generate the Cartesian product
return Object.entries(inputObject).reduce((accumulator, [key, valuesArray]) => {
const combinations: Array<{ [K in keyof TOut]: TOut[K][number] }> = [];
accumulator.forEach(existingCombination => {
valuesArray.forEach(singleValue => {
// If the value is an object, recursively get its Cartesian product
const nestedValues = (singleValue && typeof singleValue === 'object' && !Array.isArray(singleValue))
? getCartesian(singleValue as Record<string, unknown[]>)
: [singleValue];
// Combine the existing combination with each nested value
nestedValues.forEach(nestedValue => {
combinations.push({ ...existingCombination, [key]: nestedValue });
});
});
});
return combinations;
}, [{} as { [K in keyof TOut]: TOut[K][number] }]);
}