【问题标题】:python recursion list combinations without using generator不使用生成器的python递归列表组合
【发布时间】:2017-07-11 12:12:03
【问题描述】:

我正在学习python3。 为了更多地考虑递归,我想实现一个函数 comb(n, k),它返回一个列表,该列表由集合 {1,2,…,n} 中 kk 元素的所有组合组成。

我认为使用循环是不明智的,因为嵌套循环的数量取决于 k。所以我用递归来考虑它。我尝试编写受This question启发的函数 虽然我无法得到正确的答案。

def combinations(sub, data_set, index, still_needed):
    if still_needed == 0:
        return sub

    for i in range(index, len(data_set)):
        sub.append(data_set[i])
        still_needed = still_needed - 1
        return combinations(sub, data_set, index+1, still_needed)

def comb(n, k):
    data_set = list(range(1, n+1))
    print (combinations([], data_set, 0, k))

如果我测试 Comb(6,3),我只会得到 [1,2,3]。我想得到所有的组合。我的代码有什么问题?还是错过了什么重要的东西?我只是想学习python的递归,这不是功课,谢谢。


预期结果如下:

[[1, 5, 6],
[2, 5, 6],
[3, 5, 6],
[4, 5, 6],
[1, 4, 6],
[2, 4, 6],
[3, 4, 6],
[1, 3, 6],
[2, 3, 6],
[1, 2, 6],
[1, 4, 5],
[2, 4, 5],
[3, 4, 5],
[1, 3, 5],
[2, 3, 5],
[1, 2, 5],
[1, 3, 4],
[2, 3, 4],
[1, 2, 4],
[1, 2, 3]]

虽然顺序并不重要。如果有任何pythonic方法可以解决这个问题,我将不胜感激。嵌套[expression for item in iterable](因为我试过但失败了)。

再次感谢。

【问题讨论】:

  • 如果迭代(循环)不明智,因为迭代次数取决于k,那么递归肯定是不明智的,因为它是否崩溃取决于k。
  • 您可以为您的示例输入 Comb(6,3) 发布所需的结果吗?
  • 你确实得到了第一个组合。如果您需要更多,则必须选择yields 或收集所有组合(不是returns 第一个找到的组合)的实现。

标签: python algorithm recursion


【解决方案1】:

您的函数中的问题是您的for 循环中有一个return 语句:它在第一次迭代期间停止执行该函数。

这是您可以用于递归的基本结构:

def combinations(n, k, min_n=0, accumulator=None):
    if accumulator is None:
        accumulator = []
    if k == 0:
        return [accumulator]
    else:
        return [l for x in range(min_n, n)
                for l in combinations(n, k - 1, x + 1, accumulator + [x + 1])]

print(combinations(6, 3))
# [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 2, 6], [1, 3, 4], [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [1, 5, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6], [2, 5, 6], [3, 4, 5], [3, 4, 6], [3, 5, 6], [4, 5, 6]]

要检查结果是否正确,您可以针对itertools 进行测试:

import itertools
print(list(itertools.combinations(range(1,7),3)))
# [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 2, 6), (1, 3, 4), (1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (1, 5, 6), (2, 3, 4), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6), (2, 5, 6), (3, 4, 5), (3, 4, 6), (3, 5, 6), (4, 5, 6)]
print(
        list(itertools.combinations(range(1, 7), 3))
          ==
        [tuple(comb) for comb in combinations(6, 3)]
     )
# True

【讨论】:

  • 最好使用累加器列表,在函数签名中传递,而不是将新列表连接 n 次。列表连接很昂贵。
  • @danny:很有趣。累加器列表是什么意思?
  • 意思是在第一次迭代时创建一个列表来保存递归的累积结果。附加到它并通过递归调用传递它。结果是每次迭代都会附加一个列表,而不是通过串联创建新列表。例如,def combinations(n, k, min_n=0, accumulator=None) 并在附加后调用 combinations(n, k -1, x+1, accumulator=accumulator)
  • @danny:谢谢,你是这个意思吗?我认为它现在使用 n 的次数少了几倍。
  • 是的。尽管稍有改变,在函数声明中使用可变对象是不好的做法(即使在不同模块中的导入中,同一对象也会在函数的多次调用中使用,并且会产生不好的结果)。是的,itertools 在性能方面是一个更好的选择,但问题是与递归有关,这是一种常见的模式 :) 还要记住 python 的递归限制(10k)。
猜你喜欢
  • 1970-01-01
  • 2021-04-22
  • 1970-01-01
  • 2013-06-30
  • 1970-01-01
  • 1970-01-01
  • 2021-05-22
  • 2016-05-03
  • 2021-10-17
相关资源
最近更新 更多