N == N的数字和的某些幂(运行太慢)

时间:2015-09-14 01:16:17

标签: python algorithm performance sum-of-digits

我正在尝试编写一个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

我还没有研究过数据结构和算法,所以我很感激有关使这个脚本更有效的指示。

2 个答案:

答案 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个。

探查器是您的朋友,当您执行数十万次时,字符串转换不是免费的:)