【问题标题】:Why is my method using lookup table so slow compared to brute force method?与蛮力方法相比,为什么我使用查找表的方法这么慢?
【发布时间】:2014-11-15 10:55:45
【问题描述】:

这个问题是关于欧拉Problem 21

基本算法是建立一个查找表d_value,当我们从210000 时,它将存储number: d(number)

那么为了尽可能少的调用d()函数。

只要我们有一个号码,我们就会检查这个号码是否已经存在于d_value.keys() 中。

如果没有,那么我们将添加这个值为d(number) 的键。

d_number 也是如此。

然后我们比较它是否符合定义,如果符合,我们将number添加到amicable_number_sum

然后下一个number

我的代码是这样的:

from math import sqrt

def d(number):
    sum = 1
    for foo in range(2, int(sqrt(number)) + 1):
        if number % foo == 0:
            sum += foo
            sum += number/foo
    return sum


d_value = {}
amicable_number_sum = 0

for number in range(2, 10000):
    if not number in d_value.keys():
        d_value[number] = d(number)
    if not d_value[number] in d_value.keys():
        d_value[d_value[number]] = d(d_value[number])
    if number == d_value[d_value[number]] and not number == d_value[number]:
        amicable_number_sum += number

print amicable_number_sum

此代码需要 2.14 秒才能完成。

然后是我试图“避免”的蛮力方法,它需要 0.147 秒才能完成。 :(

只要我们需要知道一个数字的d(),它就会调用函数d()

from math import sqrt


def d(number):
    sum = 1
    for foo in range(2, int(sqrt(number)) + 1):
        if number % foo == 0:
            sum += foo
            sum += number/foo
    return sum


amicable_number_sum = 0

for number in range(2, 10000):
    d_value = d(number)
    if d(d_value) == number and not number == d_value:
        amicable_number_sum += number

print amicable_number_sum

那么我的代码的哪一部分一直在占用?

我的猜测是两个if 用字典键检查。

但由于我无法检查代码中的时间消耗(有吗?),我想听听您对此的看法。

【问题讨论】:

  • 如果这是一个算法问题,请尝试描述算法而不是代码。
  • @simonzack 感谢您的建议。我已将算法移至帖子的开头部分。

标签: python performance time


【解决方案1】:

时间用于字典键查找,特别是以下行:

if not number in d_value.keys():

if not d_value[number] in d_value.keys():

无需调用keys() - 只需在字典上进行简单查找即可,即将这些行更改为:

if not number in d_value:

if not d_value[number] in d_value:

您现在应该会看到优化算法的性能更快。

您可以通过在这个稍微重组的代码上使用cProfile 模块来确定这一点:

from math import sqrt

def d(number):
    sum = 1
    for foo in range(2, int(sqrt(number)) + 1):
        if number % foo == 0:
            sum += foo
            sum += number/foo
    return sum


def runit():
    d_value = {}
    amicable_number_sum = 0

    for number in range(2, 10000):
        if not number in d_value.keys():
            d_value[number] = d(number)
        if not d_value[number] in d_value.keys():
            d_value[d_value[number]] = d(d_value[number])
        if number == d_value[d_value[number]] and not number == d_value[number]:
            amicable_number_sum += number
    print amicable_number_sum

import cProfile
cProfile.run('runit()')

输出

         51632 function calls in 2.366 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.366    2.366 <string>:1(<module>)
        1    1.711    1.711    2.366    2.366 ams.py:12(runit)
    10544    0.068    0.000    0.079    0.000 ams.py:3(d)
    10544    0.003    0.000    0.003    0.000 {math.sqrt}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    19996    0.576    0.000    0.576    0.000 {method 'keys' of 'dict' objects}
    10545    0.008    0.000    0.008    0.000 {range}

这表明很多时间都花在“'dict'对象的方法'keys'”上,即插入前的字典查找。

修改为只使用value in d 后,分析输出为:

         31636 function calls in 0.109 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.109    0.109 <string>:1(<module>)
        1    0.010    0.010    0.109    0.109 ams_fixed.py:12(runit)
    10544    0.088    0.000    0.099    0.000 ams_fixed.py:3(d)
    10544    0.002    0.000    0.002    0.000 {math.sqrt}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    10545    0.009    0.000    0.009    0.000 {range}

【讨论】:

  • 哇。谢谢这绝对是这里的问题。在 10000 和 100000 的情况下,优化后的算法都快 30% 左右。那么是什么原因呢?是不是因为每当我调用keys() 时,python 都会尝试遍历现有的字典以生成一个带有键名的新列表。然而简单的查找更多......如何把这个......内置和快速?
  • 是的,我想说这就是为什么 - 生成键列表然后对结果列表执行 in 操作所花费的时间是 O(n),而“本机”查找字典键是 O(1)。
  • 乌云散去 XD。感谢您引入 cProfile 模块,这完全是我希望找到的。
猜你喜欢
  • 1970-01-01
  • 2020-04-03
  • 1970-01-01
  • 2015-07-28
  • 1970-01-01
  • 2018-03-14
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
相关资源
最近更新 更多