简体   繁体   English

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

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

I've had a look around this site but I have been unable to find an answer that includes duplicate elements.我环顾了这个网站,但找不到包含重复元素的答案。 For example, given the array:例如,给定数组:

[1,2,3,4]

With a length of 3, A function should generate a list of every single possible combination with those numbers, using each one more than once:长度为 3 时,函数应生成包含这些数字的每个可能组合的列表,并多次使用每个组合:

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

I just haven't been able to get my head around the algorithm that I should use.我只是无法理解我应该使用的算法。 I don't expect a code answer, but a push in the right direction would be appreciated.我不希望得到代码答案,但是朝着正确的方向推动将不胜感激。

I've tried using reduce like so:我试过像这样使用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
}, [])

but I really don't know how to continue.但我真的不知道如何继续。

As a side note, ideally, I would like to do this as efficiently as possible.作为旁注,理想情况下,我希望尽可能高效地执行此操作。

One approach would be to use the length of the array as a base.一种方法是使用数组的长度作为基数。 You could then just access the array's elements from 0, and count up to the amount of combinations (array length ** length).然后,您可以从 0 开始访问数组的元素,并计算组合的数量(数组长度 ** 长度)。 If you're working with a small dataset, performance really shouldn't be an issue, but this answer is very performance oriented:如果您使用的是小型数据集,性能确实不应该成为问题,但是这个答案非常注重性能:

 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)

You could take an algorithm for getting a cartesian prduct with an array of three arrays with the wanted values.您可以采用一种算法来获取具有所需值的三个数组的数组的笛卡尔产品。

 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; }

Picture an ancient mechanical rotary counter :想象一个古老的机械旋转计数器

在此处输入图片说明

To count from 00000 to 99999 you rotate the rightmost wheel until it reaches 9, then it resets to 0 and the second right wheel is advanced by 1 etc.要从0000099999您旋转最右边的轮子直到它达到 9,然后它重置为 0,第二个右轮前进 1 等。

In code: locate the rightmost wheel which position is less than the max.在代码中:找到位置小于最大值的最右边的轮子。 digit.数字。 Advance that wheel by 1 and reset all wheels on the right of it to 0. Repeat until there's no such wheel.将该轮推进 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('')))

Performance measures:表现措施:

 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) }

Using the same technique to generate cartesian products:使用相同的技术生成笛卡尔积:

 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')

Maybe also add the recursive solution:也许还添加递归解决方案:

 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("")));

This is known as "n multichoose k" and has the following recurrence relation :这被称为“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)))

An alternative, as others have answered, is to also include every permutation of every combination.正如其他人所回答的那样,另一种选择是还包括每个组合的每个排列。 One way is to append each element to each combination as we build towards the final length.一种方法是在我们朝着最终长度构建时将每个元素附加到每个组合。 (This idea is similar to trincot's.) (这个想法类似于特林科的。)

 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