【问题标题】:How to efficiently get all combinations where the sum is 10 or below in Python如何在Python中有效地获得总和为10或以下的所有组合
【发布时间】:2014-12-21 03:13:31
【问题描述】:

假设您正在尝试将一些固定资源(例如n=10)分配到一些地区(例如t=5)。我试图有效地找出如何获得总和为n 或以下的所有组合。

例如10,0,0,0,0 很好,0,0,5,5,0 等也一样,而3,3,3,3,3,3 显然是错误的。

我已经走到这一步了:

import itertools
t = 5
n = 10
r = [range(n+1)] * t
for x in itertools.product(*r): 
   if sum(x) <= n:          
       print x

不过,这种蛮力方法非常缓慢;一定有更好的办法吗?

时间(1000 次迭代):

Default (itertools.product)           --- time: 40.90 s
falsetru recursion                    --- time:  3.63 s
Aaron Williams Algorithm (impl, Tony) --- time:  0.37 s

【问题讨论】:

  • 你想找出一个数字的所有总和组合吗?
  • @qqvc 听起来对吗?
  • this one 可能重复?这里的任务是找到所有的partitions of an integer
  • @Tony 虽然有些想法可能适用,但问题是找到一个等于的值,而这个问题是查找所有内容。此外,它是用另一种语言给出的答案。我觉得必须有一些标准模块提供这个?

标签: python itertools


【解决方案1】:

可能的方法如下。绝对会谨慎使用(几乎没有经过测试,但 n=10 和 t=5 的结果看起来很合理)。

该方法涉及 no 递归。生成具有 m 个元素(在您的示例中为 5)的数字 n(在您的示例中为 10)的分区的算法来自 Knuth 的第 4 卷。然后,如有必要,每个分区都将进行零扩展,并且所有不同的排列都是使用 Aaron Williams 的算法生成的,我看到该算法引用了 elsewhere。两种算法都必须翻译成 Python,这增加了错误潜入的机会。Williams 算法需要一个链表,我不得不用二维数组来伪造它以避免编写链表类。

还有一个下午!

代码(注意你的n是我的maxn,你的t是我的p):

import itertools

def visit(a, m):
    """ Utility function to add partition to the list"""
    x.append(a[1:m+1])

def parts(a, n, m):
    """ Knuth Algorithm H, Combinatorial Algorithms, Pre-Fascicle 3B
        Finds all partitions of n having exactly m elements.
        An upper bound on running time is (3 x number of
        partitions found) + m.  Not recursive!      
    """
    while (1):
        visit(a, m)
        while a[2] < a[1]-1:
            a[1] -= 1
            a[2] += 1
            visit(a, m)
        j=3
        s = a[1]+a[2]-1
        while a[j] >= a[1]-1:
            s += a[j]
            j += 1
        if j > m:
            break
        x = a[j] + 1
        a[j] = x
        j -= 1
        while j>1:
            a[j] = x
            s -= x
            j -= 1
            a[1] = s

def distinct_perms(partition):
    """ Aaron Williams Algorithm 1, "Loopless Generation of Multiset
        Permutations by Prefix Shifts".  Finds all distinct permutations
        of a list with repeated items.  I don't follow the paper all that
        well, but it _possibly_ has a running time which is proportional
        to the number of permutations (with 3 shift operations for each  
        permutation on average).  Not recursive!
    """

    perms = []
    val = 0
    nxt = 1
    l1 = [[partition[i],i+1] for i in range(len(partition))]
    l1[-1][nxt] = None
    #print(l1)
    head = 0
    i = len(l1)-2
    afteri = i+1
    tmp = []
    tmp += [l1[head][val]]
    c = head
    while l1[c][nxt] != None:
        tmp += [l1[l1[c][nxt]][val]]
        c = l1[c][nxt]
    perms.extend([tmp])
    while (l1[afteri][nxt] != None) or (l1[afteri][val] < l1[head][val]):
        if (l1[afteri][nxt] != None) and (l1[i][val]>=l1[l1[afteri][nxt]][val]):
            beforek = afteri
        else:
            beforek = i
        k = l1[beforek][nxt]
        l1[beforek][nxt] = l1[k][nxt]
        l1[k][nxt] = head
        if l1[k][val] < l1[head][val]:
            i = k
        afteri = l1[i][nxt]
        head = k
        tmp = []
        tmp += [l1[head][val]]
        c = head
        while l1[c][nxt] != None:
            tmp += [l1[l1[c][nxt]][val]]
            c = l1[c][nxt]
        perms.extend([tmp])

    return perms

maxn = 10 # max integer to find partitions of
p = 5  # max number of items in each partition

# Find all partitions of length p or less adding up
# to maxn or less

# Special cases (Knuth's algorithm requires n and m >= 2)
x = [[i] for i in range(maxn+1)]
# Main cases: runs parts fn (maxn^2+maxn)/2 times
for i in range(2, maxn+1):
    for j in range(2, min(p+1, i+1)):
        m = j
        n = i
        a = [0, n-m+1] + [1] * (m-1) + [-1] + [0] * (n-m-1)
        parts(a, n, m)
y = []
# For each partition, add zeros if necessary and then find
# distinct permutations.  Runs distinct_perms function once
# for each partition.
for part in x:
    if len(part) < p:
        y += distinct_perms(part + [0] * (p - len(part)))
    else:
        y += distinct_perms(part)
print(y)
print(len(y))

【讨论】:

  • 确实允许零。此外,这认为所有“领土”都是完全相同的(排序无关紧要),而实际上对于我的应用程序来说确实如此。这就是为什么我认为这是一个不同的问题?除此之外,它确实非常有效:-)
  • @jterrace 是对的。鉴于上述独特的结果,您可以生成每个的不同排列。这样做可能比使用任何其他方法更快。 otherposts 展示了如何获得不同的排列。
  • 这可以产生[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
  • @falsetru 这是一个有效的结果。
  • @PascalvKooten,t=5 呢? (在一些地区(例如 t=5) 在 questino)
【解决方案2】:

创建自己的递归函数,它不会与元素递归,除非可以求和

def f(r, n, t, acc=[]):
    if t == 0:
        if n >= 0:
            yield acc
        return
    for x in r:
        if x > n:  # <---- do not recurse if sum is larger than `n`
            break
        for lst in f(r, n-x, t-1, acc + [x]):
            yield lst

t = 5
n = 10
for xs in f(range(n+1), n, 5):
    print xs

【讨论】:

  • 您的解决方案也非常棒。如此简洁,修改起来真的很容易,这样我就可以用一些预修剪来替换 [1, 2, 3, ..., n] (range(n+1)),比如 [1, 2, n/4, n/2, n]
【解决方案3】:

您可以使用itertools 创建所有排列,并使用numpy 解析结果。

>>> import numpy as np
>>> from itertools import product

>>> t = 5
>>> n = 10
>>> r = range(n+1)

# Create the product numpy array
>>> prod = np.fromiter(product(r, repeat=t), np.dtype('u1,' * t))
>>> prod = prod.view('u1').reshape(-1, t)

# Extract only permutations that satisfy a condition
>>> prod[prod.sum(axis=1) < n]

时间:

>>> %%timeit 
    prod = np.fromiter(product(r, repeat=t), np.dtype('u1,' * t))
    prod = prod.view('u1').reshape(-1, t)
    prod[prod.sum(axis=1) < n]

10 loops, best of 3: 41.6 ms per loop

您甚至可以通过populating combinations directly in numpy 加快产品计算速度。

【讨论】:

  • 这样比默认方式慢? 98ms40.9ms 每次迭代。
  • 其实在我的机器上运行1000次迭代需要2分29秒。
  • 迭代是什么意思?计算 1000 次?如果这就是你的意思,你只需要到第一行一次,第二行1000次。
  • 是的,在这里使用“迭代”并不是最清楚的。我的意思是当我们运行它 1000 次时(将 s 更改为 ms 将为您提供每次运行的时间)。总共需要 3.13 秒(这意味着一次迭代需要 3.13 毫秒)。但是,创建和处理组合也是时间的一部分。假设您更改了tn,您必须创建新的组合。虽然它看起来很有用,但您基本上只是在创造额外的开销?
  • 好吧,那我弄错了。我以为你的意思是 1000 次迭代的平均时间。那么 numpy 不是这里的最佳选择,计算数组条件的速度非常快,但它需要创建具有所有排列的数组(非常慢)。我刚刚更新了一个更快的答案。但是,如上所述,其他方法仍然更快。很抱歉造成误解;P
【解决方案4】:

您可以使用动态规划优化算法。

基本上,有一个数组a,其中a[i][j] 的意思是“我可以得到j 与元素的总和,直到j-th 元素(并使用jth 元素,假设你有你的数组中的元素t(不是你提到的数字))。

然后你可以填充数组做

a[0][t[0]] = True
for i in range(1, len(t)):
    a[i][t[i]] = True
    for j in range(t[i]+1, n+1):
         for k in range(0, i):
             if a[k][j-t[i]]:
                 a[i][j] = True

然后,使用此信息,您可以回溯解决方案 :)

def backtrack(j = len(t)-1, goal = n):
    print j, goal
    all_solutions = []
    if j == -1:
       return []
    if goal == t[j]:
       all_solutions.append([j])
    for i in range(j-1, -1, -1):
       if a[i][goal-t[j]]:
          r = backtrack(i, goal - t[j])
          for l in r:
              print l
              l.append(j)
              all_solutions.append(l)
    all_solutions.extend(backtrack(j-1, goal))
    return all_solutions


 backtrack() # is the answer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多