我已经分析了我的应用程序,它将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
我主要是寻找一种不同的算法来解决这个问题。我目前看来效率很低。但是,如果您对代码本身有优化建议,我也很乐意听到这些。
附加:
答案 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
)并且速度相同。
我不知道你为什么要使用zip
和xrange
...你没有在计算中使用结果。
编辑:我使用此版本获得了显着的加速,直到您的版本出现内存错误,而不仅仅是短名单。
答案 4 :(得分:0)
从数学的角度来看,你最终会获得起始值最大公约数的所有倍数。
例如:
答案 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