【问题标题】:python: Generating integer partitionspython:生成整数分区
【发布时间】:2012-05-01 22:02:06
【问题描述】:

我需要生成给定整数的所有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 这样的输入,它几乎冻结了我的整个系统几秒钟,然后才给出它的输出。

如果它是一个递归算法,我会尝试用缓存函数或其他东西来装饰它以提高它的效率,但是我不知道该怎么做。

你对如何加速这个算法有什么建议吗?或者您能建议我另一种方法,或者另一种方法来从头开始制作另一种方法吗?

【问题讨论】:

  • 仅仅因为计算 40 需要几秒钟,但这并不意味着它没有效率。
  • 该算法不会产生组合,它会产生分区。但这是一个幸运的错误:有 40 首 549755813888 首乐曲,这会使任何人的电脑停顿。
  • 请编辑您的问题,因为它会让那些真正在寻找整数compositions的人感到困惑。

标签: python combinatorics performance data-partitioning


【解决方案1】:

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

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 中让它稍微快一点,那么很容易将函数 sigma 替换为 1。

这里同样值得注意的是,对于任何恒定摊销时间算法,您对生成的对象所做的实际操作几乎肯定会主导生成它们的成本。例如,如果您将所有分区存储在一个列表中,那么为这个大列表管理内存所花费的时间将远远大于生成分区所花费的时间。

说,出于某种原因,您想获取每个分区的乘积。如果您对此采取幼稚的方法,则所涉及的处理在零件数量上是线性的,而生成成本是恒定的。很难想象一个组合生成算法的应用,其中处理不支配生成成本。因此,在实践中,使用 sigma(x) = x 的更简单、更通用的 ruleGen 和专用的 accelAsc 之间不会有明显的区别。

【讨论】:

【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    用 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.'
    

    如果我更改 Cython 的 accelAsc 定义以开始:

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

    我将 Cython 时间缩短到 2.27 秒。

    【讨论】:

      【解决方案4】:

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

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

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

      给:

      time: 0.03636 sec
      length: 37338
      

      【讨论】:

      • 不要像这样给 Python 计时,使用 timeit 模块。
      • @Lattyware:我不喜欢这样的 Python 时间,这不意味着是性能时间。我向 OP 展示了我无法重现他的“几秒钟冻结”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      • 1970-01-01
      • 1970-01-01
      • 2021-02-10
      • 1970-01-01
      • 1970-01-01
      • 2012-05-04
      相关资源
      最近更新 更多