使用给定的所有数字找到可以通过加法和减法进行的数字

时间:2011-08-07 09:14:51

标签: python performance algorithm

我已经分析了我的应用程序,它将90%的时间花在plus_minus_variations上。

该函数找到了使用加法和减法给出各种数字列表的方法。

例如:
输入

1, 2

输出

1+2=3
1-2=-1
-1+2=1
-1-2=-3

这是我目前的代码。我认为它在速度方面可以得到很大改善。

def plus_minus_variations(nums):
    result = dict()
    for i, ops in zip(xrange(2 ** len(nums)), \
            itertools.product([-1, 1], repeat=len(nums))):
        total = sum(map(operator.mul, ops, nums))
        result[total] = ops
    return result

我主要是寻找一种不同的算法来解决这个问题。我目前看来效率很低。但是,如果您对代码本身有优化建议,我也很乐意听到这些。

附加:

  • 如果结果错过了一些答案(或者有一些无关的答案),如果它的完成速度要快得多,那就没关系。
  • 如果有多种方法可以获得数字,那么其中任何一种方法都可以。
  • 对于我正在使用的列表大小,99.9%的方式会产生重复的数字。
  • 如果结果没有产生数字的方式,那就好了,如果它再次完成得更快。

6 个答案:

答案 0 :(得分:6)

如果没有得到数字生成的痕迹,则没有理由每次都重新计算数字组合的总和。您可以存储中间结果:

def combine(l,r):
    res = set()
    for x in l:
        for y in r:
            res.add( x+y )
            res.add( x-y )
            res.add( -x+y )
            res.add( -x-y )
    return list(res)

def pmv(nums):
    if len(nums) > 1:
        l = pmv( nums[:len(nums)/2] )
        r = pmv( nums[len(nums)/2:] )
        return combine( l, r )
    return nums

编辑:如果数字生成方式很重要,您可以使用此变体:

def combine(l,r):
    res = dict()
    for x,q in l.iteritems():
        for y,w in r.iteritems():
            if not res.has_key(x+y):
                res[x+y] = w+q
                res[-x-y] = [-i for i in res[x+y]]
            if not res.has_key(x-y):
                res[x-y] = w+[-i for i in q]
                res[-x+y] = [-i for i in res[x-y]]
    return res

def pmv(nums):
    if len(nums) > 1:
        l = pmv( nums[:len(nums)/2] )
        r = pmv( nums[len(nums)/2:] )
        return combine( l, r )
    return {nums[0]:[1]}

我的测试显示它仍然比其他解决方案更快。

答案 1 :(得分:5)

<强>编辑:

啊哈!
代码在Python 3中, 受到tyz的启发:

from functools import reduce # only in Python 3

def process(old, num):
    new = set(map(num.__add__, old)) # use itertools.imap for Python 2
    new.update(map((-num).__add__, old))
    return new

def pmv(nums):
    n = iter(nums)
    x = next(n)
    result = {x, -x} # set([x, -x]) for Python 2
    return reduce(process, n, result)

我使用reduce来逐个计算它,而不是拆分半和递归。这极大地减少了函数调用的次数。

花费不到1秒来计算256个数字。


为什么产品呢?

def pmv(nums):
    return {sum(i):i for i in itertools.product(*((num, -num) for num in nums))}

如果不生成数字,可以更快:

def pmv(nums):
    return set(map(sum, itertools.product(*((num, -num) for num in nums))))

答案 2 :(得分:4)

对于大型随机列表来说,这似乎要快得多,我想你可以进一步微优化它,但我更喜欢可读性。

我将列表分成小块并为其创建变体。由于你得到的变量比2 ** len(chunk)变量少很多,所以它会更快。块长度为6,您可以使用它来查看最佳块长度。

def pmv(nums):
    chunklen=6
    res = dict()
    res[0] = ()
    for i in xrange(0, len(nums), chunklen):
        part = plus_minus_variations(nums[i:i+chunklen])
        resnew = dict()
        for (i,j) in itertools.product(res, part):
            resnew[i + j] = tuple(list(res[i]) + list(part[j]))
        res = resnew
    return res

答案 3 :(得分:2)

只需执行以下操作即可获得50%的加速(至少对于短名单):

from itertools import product, imap
from operator import mul

def plus_minus_variations(nums):
    result = {}
    for ops in product((-1, 1), repeat=len(nums)):
        result[sum(imap(mul, ops, nums))] = ops
    return result

imap不会创建您不需要的中间列表。导入本地名称空间可以节省查找时间属性。元组比列表更快。不要存储不需要的中间项目。

我用dict理解尝试了这个,但它有点慢。我尝试了一下设置理解(不保存ops)并且速度相同。

我不知道你为什么要使用zipxrange ...你没有在计算中使用结果。

编辑:我使用此版本获得了显着的加速,直到您的版本出现内存错误,而不仅仅是短名单。

答案 4 :(得分:0)

从数学的角度来看,你最终会获得起始值最大公约数的所有倍数。

例如:

  • 起始值2,4。那么gcd(2,4)是2,所以生成的数字是.. -4,-2,0,2,4 ......
  • 起始值3,5。然后gcd(3,5)为1,你得到所有整数。
  • startvalues 12,18,15。gcd(12,15,18)是3,你得到.. -6,-3,0,3,6 ...... ....

答案 5 :(得分:0)

这个简单的迭代方法计算所有可能的总和。它可能比@tyz的递归方法快约5倍。

def pmv(nums):
    sums = set()
    sums.add(0)
    for i in range(0, len(nums)):
        partialsums = set()
        for s in sums:
            partialsums.add(s + nums[i])
            partialsums.add(s - nums[i])
        sums = partialsums
    return sums