Python 一种生成数字分区的算法

Python 一种生成数字分区的算法,python,algorithm,Python,Algorithm,我的目标是将给定数目S的所有分区按预定义值进行分解,使其总和小于S且大于0.8S。 例如,S=1000,我们想将1000分解为17x+20y+150z类型的和,这样17x+20y+150z小于1000,大于800 我遇到了一个类似的问题,但我很难理解如何将值存储到数组中 据我所知,您不希望生成S的实际分区,因为这样做没有意义: 例如,S=1000,我们想将1000分解为17x+20y+150z类型的和,这样17x+20y+150z小于1000 如果小于1000,它就不是1000的分区 所以我假设

我的目标是将给定数目S的所有分区按预定义值进行分解,使其总和小于S且大于0.8S。 例如,S=1000,我们想将1000分解为17x+20y+150z类型的和,这样17x+20y+150z小于1000,大于800


我遇到了一个类似的问题,但我很难理解如何将值存储到数组中

据我所知,您不希望生成S的实际分区,因为这样做没有意义:

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

如果小于1000,它就不是1000的分区

所以我假设您想要生成从0.8S到S的所有数字的分区

我遇到了一个类似问题的解决方案,但我很难理解如何将值存储到数组中

只需执行listpartitionsn。对于您的问题:

[list(partitions(i)) for i in range(int(0.8*S), S)]
其中分区是您链接到的函数,由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:]

据我所知,您不希望生成S的实际分区,因为这样做没有意义:

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

如果小于1000,它就不是1000的分区

所以我假设您想要生成从0.8S到S的所有数字的分区

我遇到了一个类似问题的解决方案,但我很难理解如何将值存储到数组中

只需执行listpartitionsn。对于您的问题:

[list(partitions(i)) for i in range(int(0.8*S), S)]
其中分区是您链接到的函数,由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:]

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

在这里,我发现适合您的模式的数字在990到1000(包括990到1000)范围内,以使输出易于管理,因为在800到1000范围内有1284个x、y和z的组合

我假设您想对这些解决方案做些什么,所以我将它们保存在一个列表中以供进一步处理

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)
输出

我想我应该解释一下这段略显神秘的代码:

x = max(1, -(-d // mx))
//是楼层除法运算符,a//b返回小于或等于a/b的最大整数

因此-d//mx是最大的整数=d/mx。然而,当sy>=lo时,这有时会产生非正值;出现这种情况时,max函数确保1是分配给x的最小值

在看到约翰·科尔曼的更一般的解决方案后,我也受到启发写了一个。我的不像John的那样紧凑或易读,但它使用迭代而不是递归,并且使用更少的内存。它的速度也是我的两倍,尽管它比我的原始版本慢了大约20%,因为我的原始版本只能处理3个系数

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

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)

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

在这里,我发现适合您的模式的数字在990到1000(包括990到1000)范围内,以使输出易于管理,因为在800到1000范围内有1284个x、y和z的组合

我假设您想对这些解决方案做些什么,所以我将它们保存在一个列表中以供进一步处理

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)
输出

我想我应该解释一下这段略显神秘的代码:

x = max(1, -(-d // mx))
//是楼层除法运算符,a//b返回小于或等于a/b的最大整数

因此-d//mx是最大的整数=d/mx。然而,当sy>=lo时,这有时会产生非正值;出现这种情况时,max函数确保1是分配给x的最小值

在看到约翰·科尔曼的更一般的解决方案后,我也受到启发写了一个。我的不像John的那样紧凑或易读,但它使用迭代而不是递归,并且使用更少的内存。它的速度也是我的两倍,尽管它比我的原始版本慢了大约20%,因为我的原始版本只能处理3个系数

此代码不是返回列表,而是一个生成器。因此,您可以使用re 结果,或者您可以将结果收集到一个列表或其他集合中,例如列表的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)

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

代码如下:

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

例如,allSolutions990-1871000-187[17,20150]产生的86种溶液基本上与他们在优秀答案中发现的@PM2Ring相同。allSolutions800-1871000-187,[17,20150]也找到了1284个解。

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

代码如下:

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


例如,allSolutions990-1871000-187[17,20150]产生的86种溶液基本上与他们在优秀答案中发现的@PM2Ring相同。allSolutions800-1871000-187,[17,20150]也找到了1284个解。

你似乎根本没有谈论分区,而是谈论非负整数的线性组合。我在我的答案中添加了一个通解,如果你感兴趣的话。你似乎根本不谈论分区,而是讨论非负整数的线性组合。我在我的答案中添加了一个通解,如果你感兴趣的话。在800到1000之间的所有整数的整数分区的数量是不可能大的。Wikipedia关于整数分区的文章给出了2.4 x 10^31个1000的分区。如果这是OP想要的,他们就走运了。他们似乎想要更有限的东西,但需要澄清这到底是什么。@johncolemanfwiw,JeromeKelleher在上一篇文章中用Python编写了一些不错的迭代分区代码;去年我在一个应用程序上使用了它。在800到1000之间的所有整数的整数分区的数量是不可能的大。Wikipedia关于整数分区的文章给出了2.4 x 10^31个分区,这个数字本身就是1000。如果这是OP想要的,他们就走运了。他们似乎想要更有限的东西,但需要澄清这到底是什么。@johncolemanfwiw,JeromeKelleher在上一篇文章中用Python编写了一些不错的迭代分区代码;我去年在一个节目上用过。嗯,PM 2Ring。我能说什么呢。你和蟒蛇让我开心。我刚刚开始感受到python@EdgarNavasardyan我很高兴你喜欢它。这简直是一次5分钟的黑客攻击事实上,我刚刚意识到,通过移动测试,可以稍微提高效率哦,很好。如果可以的话,我会更新你的编辑。我已经更新了你的原始答案。迭代方法比递归方法快,这一点我并不感到奇怪。或许还有一种巧妙的方法可以编写一种大量使用itertools的非递归方法。@JohnColeman:不用担心,我也对你的答案投了赞成票。希望这篇文章会很有趣,或者未来的读者会从中得到一些帮助嗯,下午2点。我能说什么呢。你和蟒蛇让我开心。我刚刚开始感受到python@EdgarNavasardyan我很高兴你喜欢它。这简直是一次5分钟的黑客攻击事实上,我刚刚意识到,通过移动测试,可以稍微提高效率哦,很好。如果可以的话,我会更新你的编辑。我已经更新了你的原始答案。迭代方法比递归方法快,这一点我并不感到奇怪。或许还有一种巧妙的方法可以编写一种大量使用itertools的非递归方法。@JohnColeman:不用担心,我也对你的答案投了赞成票。希望这篇文章会很有趣,或者未来的读者会从中得到一些帮助那相当紧凑!我刚刚写了一个迭代的一般解。它比递归代码快,但读起来有点困难。这相当紧凑!我刚刚写了一个迭代的一般解。它比递归代码快,但读起来有些困难。