我正在尝试编写一个Python脚本,它找到所有整数(N),其中N的数字之和的某个幂等于N.例如,N = 81符合条件,因为8 + 1 = 9 ,并且某个幂为9(即2)= 81.
我选择的范围是任意的。我的脚本有效,但非常非常慢。理想情况下,我想在大约6000毫秒内找到前30个这样的整数。
我的第一个解决方案:
def powerOfSum1():
listOfN = []
arange = [a for a in range(11, 1000000)] #range of potential Ns
prange = [a for a in range(2, 6)] # a range for the powers to calculate
for num in arange:
sumOfDigits = sum(map(int, str(num)))
powersOfSum = [sumOfDigits**p for p in prange]
if num in powersOfSum:
listOfN.append(num)
return listOfN
在我的第二个解决方案中,我尝试为每个sumOfDigits存储所有权限,但这并没有提高性能。
def powerOfSum2():
listOfN = []
powers= {}
for num in range(11, 1000000):
sumOfDigits = sum(map(int, str(num)))
summ = str(sumOfDigits)
if summ in powers:
if num in powers[summ]:
listOfN.append(num)
else:
powersOfSum = [sumOfDigits**p for p in range(2,6)]
powers[summ] = powersOfSum
if num in powers[summ]:
listOfN.append(num)
return listOfN
我还没有研究过数据结构和算法,所以我很感激有关使这个脚本更有效的指示。
答案 0 :(得分:4)
您的解决方案检查每个可能的整数,看它可能是一个解决方案。仅检查实际上有效的整数以查看它们是否是有效答案更有效 - 因为这些整数较少。这是做这件事的事情。但是找到30可能要花费6个多的时间 - 它们很快就会变得稀缺。
编辑 - 更新以执行Padraic Cunningham和fjarri在评论中建议的更快的数字求和,然后更新以添加几个调整,使其成为生成器,并使其成为Python-3友好。
它仍然很慢,但算法可以并行化 - 也许你可以将数字求和放在一个单独的过程中。
编辑2 - 通过快速检查基数和结果值是否等于模9来缩短一段时间。可能会有进一步的数论技巧......
import heapq
import functools
def get_powers():
heap = []
push = functools.partial(heapq.heappush, heap)
pop = functools.partial(heapq.heappop, heap)
nextbase = 3
nextbasesquared = nextbase ** 2
push((2**2, 2, 2))
while 1:
value, base, power = pop()
if base % 9 == value % 9:
r = 0
n = value
while n:
r, n = r + n % 10, n // 10
if r == base:
yield value, base, power
power += 1
value *= base
push((value, base, power))
if value > nextbasesquared:
push((nextbasesquared, nextbase, 2))
nextbase += 1
nextbasesquared = nextbase ** 2
for i, info in enumerate(get_powers(), 1):
print(i, info)
答案 1 :(得分:4)
这是打破分析器的好时机,看看你的代码在哪里花费了所有时间。为此,我在代码周围加了一个cProfiler
包装器:
#!/usr/bin/env python
import cProfile
import math
def powerOfSum1():
listOfN = []
arange = [a for a in range(11, 1000000)] #range of potential Ns
prange = [a for a in range(2, 6)] # a range for the powers to calculate
for num in arange:
sumOfDigits = sum(map(int, str(num)))
powersOfSum = [sumOfDigits**p for p in prange]
if num in powersOfSum:
listOfN.append(num)
return listOfN
def main():
cProfile.run('powerOfSum1()')
if __name__ == "__main__":
main()
运行这个,这就是我得到的:
⌁ [alex:/tmp] 44s $ python powers.py
1999993 function calls in 4.089 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.005 0.005 4.089 4.089 <string>:1(<module>)
1 0.934 0.934 4.084 4.084 powers.py:7(powerOfSum1)
999989 2.954 0.000 2.954 0.000 {map}
10 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
2 0.017 0.009 0.017 0.009 {range}
999989 0.178 0.000 0.178 0.000 {sum}
如果你看一下,似乎大部分时间花在map
调用上,你将num
转换为字符串,然后将每个数字设为int,然后求和
很有意义的是,这将是该计划的缓慢部分。你不仅要做很多事情,而且这是一个缓慢的操作:在那一行,你做一个字符串解析操作,然后在字符串中的每个字符串上映射一个int转换函数,然后你总结它们。
我敢打赌,如果你能先计算数字之和而不先进行字符串转换,那么速度会快得多。
我们试试吧。我做了一些其他更改,比如在开始时删除冗余列表推导。这就是我得到的:
#!/usr/bin/env python
#started at 47.56
import cProfile
import math
MAXNUM = 10000000
powersOf10 = [10 ** n for n in range(0, int(math.log10(MAXNUM)))]
def powerOfSum1():
listOfN = []
arange = range(11, MAXNUM) #range of potential Ns
prange = range(2, 6) # a range for the powers to calculate
for num in arange:
sumOfDigits = 0
for p in powersOf10:
sumOfDigits += num / p % 10
powersOfSum = []
curr = sumOfDigits
for p in prange:
curr = curr * sumOfDigits
if num < curr:
break
if num == curr:
listOfN.append(num)
return listOfN
def main():
cProfile.run('powerOfSum1()')
if __name__ == "__main__":
main()
cProfile
必须说什么?
⌁ [alex:/tmp] 3m42s $ python powers.py
15 function calls in 0.959 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.006 0.006 0.959 0.959 <string>:1(<module>)
1 0.936 0.936 0.953 0.953 powers.py:13(powerOfSum1)
10 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
2 0.017 0.009 0.017 0.009 {range}
4秒到0.9秒?好多了。
如果您想真正看到效果,请在arange
的上限添加一个零。在我的盒子上,原始代码需要约47秒。修改后的代码大约需要10个。
探查器是您的朋友,当您执行数十万次时,字符串转换不是免费的:)