假设我有一个包含N个成员的列表:
const list = [0, 1, 2, ...(N-1)];
我想做(N选X),从数学上讲,所以我需要创建一个函数:
const findAllCombinations = (x, list) => {
// return all x combinations of the list
};
如果X是2,我可以这样做:
const findAllCombinations = (x, list) => {
for(let i = 0; i < list.length; i++){
for(let j = i+1; j < list.length; j++){
// N choose 2
}
}
};
但不确定如何以捕获N个choose X的方式进行循环,如果可能的话,最好是 * 迭代 * 而不是 * 递归 *!但递归解决方案就可以了。
这是我的尝试,但它是错误的:
const combine = (x, list) => {
// Note: N = list.length
if(list.length < x){
throw new Error('not enough elements to combine.');
}
if (x < 1) {
return [];
}
const ret = [];
for(let v of combine(x-1, list.slice(1))){
ret.push([list[0], ...v]);
}
return ret;
}
console.log(
combine(3, ['a','b,'c','d'])
)
目标将是得到这4种组合:
[a,b,c]
[a,b,d]
[a,c,d]
[b,c,d]
...因为(4 choose 3) = 4
。
以下是我想要的输出:
combine(0,[1,2,3]) => [[]] // as N choose 0 = 1
combine(1,[1,2,3]) => [[1],[2],[3]] // as N choose 1 = N
combine(2,[1,2,3]) => [[1,2],[1,3],[2,3]]]] // as N choose N-1 = N
combine(3,[1,2,3]) => [[1,2,3]] // as N choose N = 1
要查看更好的所需输出列表,请参阅:https://gist.github.com/ORESoftware/941eabac77cd268c826d9e17ae4886fa
2条答案
按热度按时间xqnpmsa81#
下面是一种迭代方法,它使用给定集合中的索引组合。
从k个索引的初始组合开始,这些索引被原地移位/递增,以便获得长度为k的下一个组合,依此类推:
这比人们想象的要高效得多,尤其是与总是从空组合开始而不考虑k的递归算法相比(函数next()可能会被优化)。
一个更复杂的版本,它允许指定一个k值列表,以及是否允许重复,可以在这里找到(以及它上面的递归实现)。
yv5phkfx2#
结果证明我们可以避免使用递归。
我想到了Python中的
itertools.combinations
。CPython是开源的,所以我们可以看到C. https://github.com/python/cpython/blob/main/Modules/itertoolsmodule.c中的源代码
您可以看到
itertools_combinations_impl
和combinations_next
的函数定义。不过,还有一些refCount和垃圾收集器的东西,它们可能与另一种语言无关。
我尝试在TypeScript中编写一个“combinationsobject”版本,并在其后添加了一些测试(TS playground),然后很容易从编译输出中获得JS: