python:生成整数分区

时间:2012-04-20 10:08:59

标签: python combinatorics performance data-partitioning

我需要生成给定整数的所有partitions 我发现Jerome Kelleher的这个算法对它来说是最有效的算法:

def accelAsc(n):
    a = [0 for i in range(n + 1)]
    k = 1
    a[0] = 0
    y = n - 1
    while k != 0:
        x = a[k - 1] + 1
        k -= 1
        while 2*x <= y:
            a[k] = x
            y -= x
            k += 1
        l = k + 1
        while x <= y:
            a[k] = x
            a[l] = y
            yield a[:k + 2]
            x += 1
            y -= 1
        a[k] = x + y
        y = x + y - 1
        yield a[:k + 1]

参考:http://homepages.ed.ac.uk/jkellehe/partitions.php

顺便说一句,效率不高。对于像40这样的输入,它在给出输出之前几乎冻结了整个系统几秒钟。

如果它是一个递归算法,我会尝试使用缓存函数来装饰它,或者用它来提高效率,但就像我无法弄清楚要做什么。

您对如何加快此算法有一些建议吗?或者你可以建议我另一个,或者从头开始另一个方法吗?

4 个答案:

答案 0 :(得分:10)

要直接生成合成,您可以使用以下算法:

def ruleGen(n, m, sigma):
    """
    Generates all interpart restricted compositions of n with first part
    >= m using restriction function sigma. See Kelleher 2006, 'Encoding
    partitions as ascending compositions' chapters 3 and 4 for details.
    """
    a = [0 for i in range(n + 1)]
    k = 1
    a[0] = m - 1
    a[1] = n - m + 1
    while k != 0:
        x = a[k - 1] + 1
        y = a[k] - 1
        k -= 1
        while sigma(x) <= y:
            a[k] = x
            x = sigma(x)
            y -= x
            k += 1
        a[k] = x + y
        yield a[:k + 1]

该算法非常通用,可以生成许多不同类型的分区和组合。对于您的情况,请使用

ruleGen(n, 1, lambda x: 1)

生成所有不受限制的合成。第三个参数称为限制函数,它描述了您需要的组合/分区的类型。该方法是有效的,因为当您对生成的所有组合物进行平均时,生成每种组合物所需的努力量是恒定的。如果你想在python中稍快一点,那么用1替换函数sigma很容易。

值得注意的是,对于任何常量摊销时间算法,您实际使用生成的对象所做的几乎肯定会主导生成它们的成本。例如,如果将所有分区存储在列表中,则管理此大型列表的内存所花费的时间将远远大于生成分区所花费的时间。

说,出于某种原因,您想要获取每个分区的产品。如果你对此采取一种天真的方法,那么所涉及的处理在部件数量上是线性的,而生成成本是恒定的。很难想到组合生成算法的应用,其中处理不会主导生成成本。因此,在实践中,使用更简单和更通用的ruleGen与sigma(x)= x和专用的accelAsc之间没有可衡量的差异。

答案 1 :(得分:3)

如果要对相同的输入重复使用此函数,仍然值得缓存返回值(如果要在不同的运行中使用它,则可以将结果存储在文件中)。

如果你找不到一个明显更快的算法,那么应该可以通过将代码移动到C扩展来加速一个数量级或两个(这可能是使用cython最简单的)或者通过使用PyPy代替CPython(PyPy有其缺点 - 它还不支持Python 3,或者一些常用的库,如numpy和scipy)。

原因是,由于python是动态类型的,解释器可能大部分时间都在检查变量的类型 - 对于所有解释器都知道,其中一个操作可以将x转换为字符串,在这种情况下,像x + y这样的表达式会突然产生非常不同的含义。 Cython通过允许您将变量静态声明为整数来解决这个问题,而PyPy有just-in-time compiler可以最小化冗余类型检查。

答案 2 :(得分:2)

使用n = 75进行测试我得到:

PyPy 1.8:

w:\>c:\pypy-1.8\pypy.exe pstst.py
1.04800009727 secs.

CPython 2.6:

w:\>python pstst.py
5.86199998856 secs.

Cython + mingw + gcc 4.6.2:

w:\pstst> python -c "import pstst;pstst.run()"
4.06399989128

我认为Psyco没有区别(?)

运行功能:

def run():
    import time
    start = time.time()
    for p in accelAsc(75):
        pass
    print time.time() - start, 'secs.'

如果我将Cycel的accelAsc的定义更改为:

def accelAsc(int n):
    cdef int x, y, k
    # no more changes..

我将Cython时间缩短到2.27秒。

答案 3 :(得分:0)

我会说你的性能问题出在其他地方。

我没有将它与其他方法进行比较,但它对我来说似乎有效:

import time

start = time.time()
partitions = list(accelAsc(40))
print('time: {:.5f} sec'.format(time.time() - start))
print('length:', len(partitions))

Gave:

time: 0.03636 sec
length: 37338