繁体   English   中英

如何生成一个包含重复序列的每个排列的数组?

[英]How to generate an array of every permutation of a sequence, with duplicates?

我环顾了这个网站,但找不到包含重复元素的答案。 例如,给定数组:

[1,2,3,4]

长度为 3 时,函数应生成包含这些数字的每个可能组合的列表,并多次使用每个组合:

[
  [1,1,1],
  [1,1,2],
  [1,1,3],
  ...
  [4,4,2],
  [4,4,3],
  [4,4,4]
]

我只是无法理解我应该使用的算法。 我不希望得到代码答案,但是朝着正确的方向推动将不胜感激。

我试过像这样使用reduce:

const arr = [1, 2, 3, 4]
const len = 3

arr.reduce((acc, n) => {
    for (let i = 0; i < len; i++) {
        acc.push(/* ???? */)
    }
    return acc
}, [])

但我真的不知道如何继续。

作为旁注,理想情况下,我希望尽可能高效地执行此操作。

一种方法是使用数组的长度作为基数。 然后,您可以从 0 开始访问数组的元素,并计算组合的数量(数组长度 ** 长度)。 如果您使用的是小型数据集,性能确实不应该成为问题,但是这个答案非常注重性能:

 const getCombos = (arr, len) => { const base = arr.length const counter = Array(len).fill(base === 1 ? arr[0] : 0) if (base === 1) return [counter] const combos = [] const increment = i => { if (counter[i] === base - 1) { counter[i] = 0 increment(i - 1) } else { counter[i]++ } } for (let i = base ** len; i--;) { const combo = [] for (let j = 0; j < counter.length; j++) { combo.push(arr[counter[j]]) } combos.push(combo) increment(counter.length - 1) } return combos } const combos = getCombos([1, 2, 3, 4], 3) console.log(combos)

您可以采用一种算法来获取具有所需值的三个数组的数组的笛卡尔产品。

 var values = [1, 2, 3, 4], length = 3, result = Array .from({ length }, _ => values) .reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])); console.log(result.map(a => a.join(' ')));
 .as-console-wrapper { max-height: 100% !important; top: 0; }

想象一个古老的机械旋转计数器

在此处输入图片说明

要从0000099999您旋转最右边的轮子直到它达到 9,然后它重置为 0,第二个右轮前进 1 等。

在代码中:找到位置小于最大值的最右边的轮子。 数字。 将该轮推进 1 并将其右侧的所有轮重置为 0。重复直到没有这样的轮。

 function counter(digits, size) { let wheels = Array(size).fill(0), len = digits.length, res = []; while (1) { res.push(wheels.map(n => digits[n])); for (let i = size - 1; i >= 0; i--) { if (wheels[i] < len - 1) { wheels[i]++; wheels.fill(0, i + 1); break; } if (i === 0) return res; } } } all = counter('1234', 3) console.log(...all.map(c => c.join('')))

表现措施:

 const kobe = (arr, len) => { const base = arr.length const counter = Array(len).fill(base === 1 ? arr[0] : 0) if (base === 1) return [counter] const combos = [] const increment = i => { if (counter[i] === base - 1) { counter[i] = 0 increment(i - 1) } else { counter[i]++ } } for (let i = base ** len; i--;) { const combo = [] for (let j = 0; j < counter.length; j++) { combo.push(arr[counter[j]]) } combos.push(combo) increment(counter.length - 1) } return combos } function nina(values, length) { return Array .from({length}, _ => values) .reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])); } function trincot(digits, size) { if (!size) return [[]]; // base case let shorter = trincot(digits, size - 1); // get all solutions for smaller size // ...and prefix each of those with each possible digit return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat(); } function georg(digits, size) { let wheels = Array(size).fill(0), len = digits.length, res = []; while (1) { res.push(wheels.map(n => digits[n])); for (let i = size - 1; i >= 0; i--) { if (wheels[i] < len - 1) { wheels[i]++; wheels.fill(0, i + 1); break; } if (i === 0) return res; } } } const gilad = (A, k) => k ? gilad(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]] ////////////////////////////////////////////////////////////// fns = [kobe, nina, trincot, georg, gilad]; ary = [0, 1, 2, 3, 4, 5, 6, 7, 8] size = 5 res = [] for (f of fns) { console.time(f.name); res.push(f(ary, size)); console.timeEnd(f.name) }

使用相同的技术生成笛卡尔积:

 function product(...arrays) { let size = arrays.length, wheels = Array(size).fill(0), lens = arrays.map(a => a.length), res = []; while (1) { res.push(wheels.map((n, w) => arrays[w][n])); for (let w = size - 1; w >= 0; w--) { if (wheels[w] < lens[w] - 1) { wheels[w]++; wheels.fill(0, w + 1); break; } if (w === 0) return res; } } } // demo p = product('12', 'abcde', 'XYZ') console.log(...p.map(q => q.join(''))) // some timings // https://stackoverflow.com/a/43053803/989121 const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e)))); const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a); arrays = Array(7).fill(0).map(_ => Array(5).fill(0)) // 5**7=78125 combinations console.time('func') cartesian(...arrays) console.timeEnd('func') console.time('iter') product(...arrays) console.timeEnd('iter')

也许还添加递归解决方案:

 function counter(digits, size) { if (!size) return [[]]; // base case let shorter = counter(digits, size-1); // get all solutions for smaller size // ...and prefix each of those with each possible digit return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat(); } // demo let all = counter("1234", 3); console.log(...all.map(c => c.join("")));

这被称为“n multichoose k”并具有以下递推关系

 function f(ns, n, k){ if (n == 0) return [] if (k == 0) return [[]] return f(ns, n - 1, k).concat( f(ns, n, k - 1).map(s => s.concat(ns[n-1]))) } var multiset = [1, 2, 3, 4] var k = 3 console.log(JSON.stringify(f(multiset, multiset.length, k)))

正如其他人所回答的那样,另一种选择是还包括每个组合的每个排列。 一种方法是在我们朝着最终长度构建时将每个元素附加到每个组合。 (这个想法类似于特林科的。)

 const f = (A, k) => k ? f(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]] var A = [1, 2, 3, 4] var k = 3 console.log(JSON.stringify(f(A, k)))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM