【问题标题】:Generate all possible lists of length N that sum to S in Python在 Python 中生成所有可能的长度为 N 且总和为 S 的列表
【发布时间】:2011-12-06 14:24:22
【问题描述】:

我正在尝试生成所有可能的长度为 N 且总和为 S 的列表。我已经编写了一些代码来执行此操作,但是对于任何大的(特别是,我想要 N=5,S=100),我遇到内存溢出错误。

我正在寻找更好的解决方案,或者改进我的代码的方法,以便我可以在 N=5、S=100 上运行它。下面这两个程序协同工作,在嵌套列表中创建所有可能的数字组合,然后将它们重新加工成正确的格式。下面复制了一些示例输出。

我知道我的代码不是最好的。我是一名工程师(我知道,我知道),所以编码并不是我的专长。感谢您提供的任何帮助。

编辑:我只是想澄清一些事情。首先,列表中可以有零,列表可以包含相同数字的倍数,并且列表中数字的顺序很重要。

def nToSum(N,S):
    ''' Creates a nested list of all possible lists of length N that sum to S'''
    if N <= 1: #base case
        return [S]
    else:
        L = []
        for x in range(S+1):   #create a sub-list for each possible entry of 0 to S 
            L += [[x,nToSum(N-1,S-x)]]  #create a sub-list for this value recursively
        return L

def compress(n=[],L): #designed to take in a list generated by nToSum
    '''takes the input from nToSum as list L, and then flattens it so that each list is a
       top level list.  Leading set n is the "prefix" list, and grows as you climb down the 
       sublists'''
    if type(L[0]) == int:  #base case:  you have exposed a pure integer
        return [n+L]       #take that integer, and prepend the leading set n
    else:
        Q = []
        for x in L:  # look at every sublist
            Q += compress(n+[x[0]],x[1])  # for each sublist, create top level lists recursively
        return Q                          # note:  append x[0] to leading set n

>>> nToSum(3,3)
[[0, [[0, [3]], [1, [2]], [2, [1]], [3, [0]]]], [1, [[0, [2]], [1, [1]], [2, [0]]]], [2, [[0, [1]], [1, [0]]]], [3, [[0, [0]]]]]

>>> compress([],nToSum(3,3))
[[0, 0, 3], [0, 1, 2], [0, 2, 1], [0, 3, 0], [1, 0, 2], [1, 1, 1], [1, 2, 0], [2, 0, 1], [2, 1, 0], [3, 0, 0]]

【问题讨论】:

标签: python list recursion sum


【解决方案1】:

使用生成器可以节省内存(如果使用 Python 2,则使用xrange 而不是range)。这就是我想出的。它与您的nToSum 非常相似,不需要compress

def sums(length, total_sum):
    if length == 1:
        yield (total_sum,)
    else:
        for value in range(total_sum + 1):
            for permutation in sums(length - 1, total_sum - value):
                yield (value,) + permutation

L = list(sums(5,100))
print('total permutations:',len(L))

# First and last 10 of list
for i in L[:10] + L[-10:]:
    print(i)

输出

total permutations: 4598126
(0, 0, 0, 0, 100)
(0, 0, 0, 1, 99)
(0, 0, 0, 2, 98)
(0, 0, 0, 3, 97)
(0, 0, 0, 4, 96)
(0, 0, 0, 5, 95)
(0, 0, 0, 6, 94)
(0, 0, 0, 7, 93)
(0, 0, 0, 8, 92)
(0, 0, 0, 9, 91)
(98, 0, 2, 0, 0)
(98, 1, 0, 0, 1)
(98, 1, 0, 1, 0)
(98, 1, 1, 0, 0)
(98, 2, 0, 0, 0)
(99, 0, 0, 0, 1)
(99, 0, 0, 1, 0)
(99, 0, 1, 0, 0)
(99, 1, 0, 0, 0)
(100, 0, 0, 0, 0)

【讨论】:

  • 非常感谢!这正是我所需要的。我喜欢使用双循环。感谢您向我介绍 Python 生成器的世界。我过去听说过它们,但这是我第一次看到它们的真正用途! (我想我需要出去写更多代码!)
  • 我在 Windows 64/8gb 桌面上看到 f(6,100) 的内存错误。有没有办法通过使用字典等来提高速度?
  • @Stat-R list(f(6,100)) 实例化所有排列。 f(6,100) 有 96,560,646 个排列,一个六元组使用 96 字节的内存。那是超过9GB的内存。仅计算它们,sum(1 for i in f(6,100)) 有效。
  • 如何将其转换为迭代解决方案?
【解决方案2】:

我一直在寻找与此问题类似的内容,但在 N 中的每个 n 的选项中增加了约束的复杂性。这是我的方法:

例如,如果我们正在寻找来自 10 to 50 的 5 个数字的排列,它们加起来为 100

def permutations_w_constraints(n_perm_elements, sum_total, min_value, max_value):
    # base case
    if n_perm_elements == 1:
        if (sum_total <= max_value) & (sum_total >= min_value):
            yield (sum_total,)
    else:
        for value in range(min_value, max_value + 1):
            for permutation in permutations_w_constraints(
                n_perm_elements - 1, sum_total - value, min_value, max_value
            ):
                yield (value,) + permutation

results = list(permutations_w_constraints(5, 100, 10, 50))

print('total permutations:',len(results))
for i in results[:10] + results[-10:]:
    print(i)
total permutations: 312676
(10, 10, 10, 20, 50)
(10, 10, 10, 21, 49)
(10, 10, 10, 22, 48)
(10, 10, 10, 23, 47)
(10, 10, 10, 24, 46)
(10, 10, 10, 25, 45)
(10, 10, 10, 26, 44)
(10, 10, 10, 27, 43)
(10, 10, 10, 28, 42)
(10, 10, 10, 29, 41)
(50, 18, 10, 10, 12)
(50, 18, 10, 11, 11)
(50, 18, 10, 12, 10)
(50, 18, 11, 10, 11)
(50, 18, 11, 11, 10)
(50, 18, 12, 10, 10)
(50, 19, 10, 10, 11)
(50, 19, 10, 11, 10)
(50, 19, 11, 10, 10)
(50, 20, 10, 10, 10)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-02
    相关资源
    最近更新 更多