生成数字分区的算法

时间:2016-07-01 14:56:21

标签: python algorithm

我的目标是通过预定义的值分解给定数量S的所有分区,使得总和小于S且大于0.8S。 例如,S = 1000,我们想要将1000分解为17x + 20y + 150z类型的总和,以便17x + 20y + 150z小于1000且大于800.

我遇到类似问题的solution,但我无法理解如何将值存储到数组中。

3 个答案:

答案 0 :(得分:3)

这里不需要完整的分区算法。您可以通过简单的循环找到所需的数字。如果你有一个固定数量的系数,如问题所示,那么你可以使用几个for循环。如果系数的数量可以变化,那么你需要一个更复杂的解决方案。

在这里,我找到适合您的模式的数字在990到1000(含)范围内,以使输出易于管理,因为x,y和z有1284种组合,范围从800到1000.

我假设你想用这些解决方案做某些事情,所以我将它们保存在列表中以便进一步处理。

from itertools import count

mx, my, mz = 17, 20, 150
lo = 990
hi = 1000

solns = []
for z in count(1):
    sz = z * mz
    if sz > hi:
        break
    for y in count(1):
        sy = sz + y * my
        if sy > hi:
            break
        d = lo - sy
        x = max(1, -(-d // mx))
        for x in count(x):
            s = sy + x * mx
            if s > hi:
                break
            t = (z, y, x, s)
            solns.append(t)

print(len(solns))
for t in solns:
    print(t)

<强>输出

86
(1, 3, 46, 992)
(1, 4, 45, 995)
(1, 5, 44, 998)
(1, 8, 40, 990)
(1, 9, 39, 993)
(1, 10, 38, 996)
(1, 11, 37, 999)
(1, 14, 33, 991)
(1, 15, 32, 994)
(1, 16, 31, 997)
(1, 17, 30, 1000)
(1, 20, 26, 992)
(1, 21, 25, 995)
(1, 22, 24, 998)
(1, 25, 20, 990)
(1, 26, 19, 993)
(1, 27, 18, 996)
(1, 28, 17, 999)
(1, 31, 13, 991)
(1, 32, 12, 994)
(1, 33, 11, 997)
(1, 34, 10, 1000)
(1, 37, 6, 992)
(1, 38, 5, 995)
(1, 39, 4, 998)
(2, 1, 40, 1000)
(2, 4, 36, 992)
(2, 5, 35, 995)
(2, 6, 34, 998)
(2, 9, 30, 990)
(2, 10, 29, 993)
(2, 11, 28, 996)
(2, 12, 27, 999)
(2, 15, 23, 991)
(2, 16, 22, 994)
(2, 17, 21, 997)
(2, 18, 20, 1000)
(2, 21, 16, 992)
(2, 22, 15, 995)
(2, 23, 14, 998)
(2, 26, 10, 990)
(2, 27, 9, 993)
(2, 28, 8, 996)
(2, 29, 7, 999)
(2, 32, 3, 991)
(2, 33, 2, 994)
(2, 34, 1, 997)
(3, 1, 31, 997)
(3, 2, 30, 1000)
(3, 5, 26, 992)
(3, 6, 25, 995)
(3, 7, 24, 998)
(3, 10, 20, 990)
(3, 11, 19, 993)
(3, 12, 18, 996)
(3, 13, 17, 999)
(3, 16, 13, 991)
(3, 17, 12, 994)
(3, 18, 11, 997)
(3, 19, 10, 1000)
(3, 22, 6, 992)
(3, 23, 5, 995)
(3, 24, 4, 998)
(4, 1, 22, 994)
(4, 2, 21, 997)
(4, 3, 20, 1000)
(4, 6, 16, 992)
(4, 7, 15, 995)
(4, 8, 14, 998)
(4, 11, 10, 990)
(4, 12, 9, 993)
(4, 13, 8, 996)
(4, 14, 7, 999)
(4, 17, 3, 991)
(4, 18, 2, 994)
(4, 19, 1, 997)
(5, 1, 13, 991)
(5, 2, 12, 994)
(5, 3, 11, 997)
(5, 4, 10, 1000)
(5, 7, 6, 992)
(5, 8, 5, 995)
(5, 9, 4, 998)
(6, 2, 3, 991)
(6, 3, 2, 994)
(6, 4, 1, 997)

我想我应该解释一下这个有点神秘的代码:

x = max(1, -(-d // mx))

//是底层除法运算符,a // b返回小于或等于a/b的最大整数。

因此-d // mx是最大的整数&lt; = -d/mx,因此-(-d // mx)最低整数&gt; = d/mx。但是,这有时会产生非正值(sy> = lo时);当发生这种情况时,max函数可确保1是分配给x的最低值。

在看到John Coleman的更一般的解决方案之后,我也受到了启发。我不像约翰那样紧凑或易于阅读,但它使用迭代而不是递归,并且它使用更少的内存。它的速度也快了两倍,虽然比我原来只能处理3个系数的版本大约慢了20%。

此代码不是返回列表,而是生成器。因此,您可以在结果生成时使用结果,也可以将结果收集到列表或其他集合中,例如dict列表,每个列表包含与给定总和相对应的元组,其中总和作为该列表的关键。

def linear_sum(lo, hi, coeff):
    ''' Find all positive integer solutions of the linear equation with 
        coefficients `coeff` with sum `s`: lo <= s <= hi 
    '''
    num = len(coeff)
    vector = [1] * num
    mx = coeff[-1]
    s = sum(coeff[:-1])
    while True:
        olds = s
        xlo = max(1, -((s - lo) // mx))
        xhi = 1 + (hi - s) // mx
        s += mx * xlo
        for vector[-1] in range(xlo, xhi):
            yield s, tuple(vector)
            s += mx

        # Increment next vector component
        k = num - 2
        vector[k] += 1
        s = olds + coeff[k]

        # If the component is too high 
        while s > hi:
            if not k:
                return

            # reset this component,
            s -= coeff[k] * (vector[k] - 1)
            vector[k] = 1

            # and increment the next component.
            k -= 1
            vector[k] += 1
            s += coeff[k]

# Tests

coeff = 150, 20, 17

# Create a list    
solns = [v for v in linear_sum(800, 1000, coeff)]
print(len(solns))

# Generate solutions one by one and verify that they give the correct sum
for s, vector in linear_sum(990, 1000, coeff):
    assert s == sum(u*v for u, v in zip(coeff, vector))
    print(s, vector)

<强>输出

1284
992 (1, 3, 46)
995 (1, 4, 45)
998 (1, 5, 44)
990 (1, 8, 40)
993 (1, 9, 39)
996 (1, 10, 38)
999 (1, 11, 37)
991 (1, 14, 33)
994 (1, 15, 32)
997 (1, 16, 31)
1000 (1, 17, 30)
992 (1, 20, 26)
995 (1, 21, 25)
998 (1, 22, 24)
990 (1, 25, 20)
993 (1, 26, 19)
996 (1, 27, 18)
999 (1, 28, 17)
991 (1, 31, 13)
994 (1, 32, 12)
997 (1, 33, 11)
1000 (1, 34, 10)
992 (1, 37, 6)
995 (1, 38, 5)
998 (1, 39, 4)
1000 (2, 1, 40)
992 (2, 4, 36)
995 (2, 5, 35)
998 (2, 6, 34)
990 (2, 9, 30)
993 (2, 10, 29)
996 (2, 11, 28)
999 (2, 12, 27)
991 (2, 15, 23)
994 (2, 16, 22)
997 (2, 17, 21)
1000 (2, 18, 20)
992 (2, 21, 16)
995 (2, 22, 15)
998 (2, 23, 14)
990 (2, 26, 10)
993 (2, 27, 9)
996 (2, 28, 8)
999 (2, 29, 7)
991 (2, 32, 3)
994 (2, 33, 2)
997 (2, 34, 1)
997 (3, 1, 31)
1000 (3, 2, 30)
992 (3, 5, 26)
995 (3, 6, 25)
998 (3, 7, 24)
990 (3, 10, 20)
993 (3, 11, 19)
996 (3, 12, 18)
999 (3, 13, 17)
991 (3, 16, 13)
994 (3, 17, 12)
997 (3, 18, 11)
1000 (3, 19, 10)
992 (3, 22, 6)
995 (3, 23, 5)
998 (3, 24, 4)
994 (4, 1, 22)
997 (4, 2, 21)
1000 (4, 3, 20)
992 (4, 6, 16)
995 (4, 7, 15)
998 (4, 8, 14)
990 (4, 11, 10)
993 (4, 12, 9)
996 (4, 13, 8)
999 (4, 14, 7)
991 (4, 17, 3)
994 (4, 18, 2)
997 (4, 19, 1)
991 (5, 1, 13)
994 (5, 2, 12)
997 (5, 3, 11)
1000 (5, 4, 10)
992 (5, 7, 6)
995 (5, 8, 5)
998 (5, 9, 4)
991 (6, 2, 3)
994 (6, 3, 2)
997 (6, 4, 1)

答案 1 :(得分:2)

这是一种递归方法,给定下限a,上限,b和系数列表nums,返回非负向量列表当乘以相应系数然后求和时,返回ab之间的总和。该函数允许0作为值。但请注意,例如,整数解(x,y,z)x,y,z >= 1

之间存在简单的一对一对应关系
990 <= 17x + 20y + 150z <= 1000

以及(x,y,z)

的解决方案x,y,z >= 0
990 - 187 <= 17x + 20y + 150z <= 1000 - 187

以下是代码:

import math

def allSolutions(a,b,nums):
    if len(nums) == 0:
        return []

    c = nums[0]
    m = max(0,math.ceil(a/c))
    M = math.floor(b/c)

    if len(nums) == 1:
        return [(x,) for x in range(m,M+1)]

    solutions = []
    for x in range(M+1):
        solutions.extend((x,)+s for s in allSolutions(a-c*x,b-c*x,nums[1:]))
    return solutions

例如,allSolutions(990-187,1000-187,[17,20,150])产生与@ PM2Ring在其出色答案中找到的基本相同的86解决方案。 allSolutions(800-187,1000-187,[17,20,150])也会找到1284个解决方案。

答案 2 :(得分:1)

根据我的理解,您不想生成S的实际分区,因为这样就没有意义了:

  

例如,S = 1000,我们想要将1000分解为17x + 20y + 150z类型的总和,以便17x + 20y + 150z小于1000

如果它小于1000,则它不会成为1000的分区。

因此,我假设您要生成从0.8SS的所有数字的分区。

  

我遇到了类似问题的解决方案,但我无法理解如何将值存储到数组中。

只需list(partitions(n))。对于你的问题:

[list(partitions(i)) for i in range(int(0.8*S), S)]

partitions是您链接的功能,由David Eppstein发布,我将在下面复制:

def partitions(n):
    # base case of recursion: zero is the sum of the empty list
    if n == 0:
        yield []
        return

    # modify partitions of n-1 to form partitions of n
    for p in partitions(n-1):
        yield [1] + p
        if p and (len(p) < 2 or p[1] > p[0]):
            yield [p[0] + 1] + p[1:]