提高Python 3.4中大字典的速度

提高Python 3.4中大字典的速度,python,performance,dictionary,Python,Performance,Dictionary,因此,我正在研究一个有1000000个键的字典,我的任务是让它在3秒内工作(在英特尔2.4GHz上)。我试着分析我的代码,虽然while循环有很多命中率,但我想不出一种方法来让我的代码在没有它的情况下运行得更快。有没有办法改进我的代码,让它更快地工作 代码应该(它这样做了,但速度太慢)生成一个字典,其中键都是2到999999之间的整数,值是由序列模式生成的列表的长度。模式是:如果整数为偶数,则将其除以2;如果整数为奇数且大于1,则将其乘以3并加1。这一直持续到我们到达1号 示例:3->10->5

因此,我正在研究一个有1000000个键的字典,我的任务是让它在3秒内工作(在英特尔2.4GHz上)。我试着分析我的代码,虽然while循环有很多命中率,但我想不出一种方法来让我的代码在没有它的情况下运行得更快。有没有办法改进我的代码,让它更快地工作

代码应该(它这样做了,但速度太慢)生成一个字典,其中键都是2到999999之间的整数,值是由序列模式生成的列表的长度。模式是:如果整数为偶数,则将其除以2;如果整数为奇数且大于1,则将其乘以3并加1。这一直持续到我们到达1号

示例:3->10->5->16->8->4->2->1。这个列表的长度是8

代码:

import time
start = time.clock()

first = 2
last = 1000000

def function1(n,d):
    if n/2 in d:
        d[n] = d[n/2] + 1
    if n not in d:
        d[n] = 0
        temp = n
        while temp > 1:
            if temp%2 == 0:
                temp /= 2
                d[n] += 1
            else:
                temp = 3*temp + 1
                d[n] += 1
            if temp in d:
                d[n] += d[temp]
                break
    return d[n]

d={}
d[1]=1
d={key: function1(key,d) for key in range(first,last)}

print(time.clock() - start)
<>这是。你可以检查一些关于互联网的信息,一些关于它的C或C++代码,也许是Python。我想你会发现人们以前做过的一些有用的功能

你们也可以使用这个模块,你们可以用它来做一些公式,我想用这个模块会更快。Numpy是一个你们可以轻松做数学运算的模块


在我的系统上,您的代码确实比3秒多了几分之一秒(在最近的2.3 GHz Intel Core i7 Macbook Pro上使用Python 3.4)

通过使用局部变量并避免构建字典两次,我可以在3秒(到2.65秒,减少12%)内完成:

def function1(n,d):
    if n/2 in d:
        d[n] = d[n/2] + 1
        return
    if n not in d:
        length = 0
        temp = n
        while temp > 1:
            if temp%2 == 0:
                temp //= 2
            else:
                temp = 3*temp + 1
            length += 1
            if temp in d:
                length += d[temp]
                break
        d[n] = length

d={1: 1}
for key in range(first,last):
    function1(key, d)
注意,我使用了一个本地
length
变量,而不是一直从
d[n]
读取长度。Python中的局部变量存储在C数组中,避免了散列键和查找(可能包括散列冲突)

我从
/
(浮点除法)切换到
/
(整数除法);当您感兴趣的只是整数结果时,不需要处理小数点

如果在字典中找到
n/2
,我也会返回。在测试成功后,测试
n不在d
中没有意义,因为我们刚刚添加了
d[n]

字典理解是完全多余的,
function1()
已经改变了
d
,因此没有必要构建新字典来替换现有结果

下一步是利用刚刚计算的
temp
值序列。当您从
3
开始时,您会计算沿途的几个其他值;完成后,所有这些都可以存储在
d
中,因此您不必重新计算
10
5
16
8
4
的顺序:

这里
3
需要8个步骤,但是我们可以为
10
存储7个步骤,为
5
存储6个步骤,等等

我放弃了
if n/2 in d
测试,而
while
循环已经解决了这个问题。由于
n
如果n不在d
块中,则不再需要
n
,因此我完全放弃了
temp
,只需继续
n


现在整个测试只需1.75秒。

我相信一个有用的优化(在1.3 GHz的MB Air上获得2.4秒,在1.3 GHz下使用Core i5,使用Python 2.7.3运行3次最好;使用3.4.1,2.7秒)是为了避免“浪费”
temp
的各种计算——保留它们可以让您非常直接地计算它们的
d
值。这是我的版本…:

import time
start = time.clock()

first = 2
last = 1000000

def function1(n, d):
    if n%2 == 0 and n//2 in d:
        d[n] = d[n//2] + 1
    if n not in d:
        temp = n
        intermediates = []
        while temp > 1:
            if temp%2 == 0:
                temp //= 2
            else:
                temp = 3 * temp + 1
            if temp in d:
                d[n] = res = d[temp] + len(intermediates) + 1
                for i, temp in enumerate(intermediates, 1):
                    d[temp] = res - i
                return res
            else:
                intermediates.append(temp)
    return d[n]

d={1: 1}
for k in range(first, last): function1(k, d)

print(time.clock() - start)

其中键都是2到999999之间的整数。那么为什么不使用列表呢?你为什么在这里用字典理解取代
d
<代码>函数1已经在
d
@MartijnPieters中创建了条目,因为这是一项大学作业,我们应该使用字典来完成。此外,我阅读了有关词典理解的内容,发现我的代码使用它运行得更快。您可以将词典理解替换为
用于范围内的键(first,last):function1(key,d)
,并省略函数中的
return d[n]
行。您已经在函数中操作了
d
,完成后无需创建一个全新的字典来替换旧字典。这只是一项繁忙的工作,徒劳地创建了一个一百万键的大词典。我现在已经尝试过了,但它运行的时间与使用词典理解的时间差不多。这看起来像Collatz的
3n+1
猜想。为了有意义,生成的序列需要以树的形式存储,因此是字典而不是列表。@JanneKarila和值是由序列组成的列表的长度pattern@grrr字典中的值。我正在编字典。然而,被称为Collatz猜想的部分是有用的。你引用的是描述。代码只计算列表的长度,没有创建列表。很抱歉,我没有意识到这一点,让我修复我的answer@JanneKarila,有趣的是(我在发布时还没有读到这篇评论),我的回答确实明确地“创建列表”--这是一个有用的优化,Martijn也在他接受的答案的编辑中采用了它。另一个有趣的优化是避免“浪费”所有
temp
的计算--将它们保存在中间结果列表中,然后重新计算它们的
d
值。使用1.3 GHz Core i5在我的MB Air上运行2.7秒。我最好把它作为可读性的答案发布…@AlexMartelli:是的,一旦完成,这些附加值也可以存储在
d
中。@MartijnPieters,我发布了我的版本,我看到你已经编辑了你的版本以使用相同的想法。当
n
现在很奇怪时,我不确定您是否真的在设置
d[n]
;嗯,除了你错了之外,因为你的测试
如果d:
中的n//2太松弛了,例如,
3//2
1
所以
d[3]
被错误地设置为
2import time
start = time.clock()

first = 2
last = 1000000

def function1(n, d):
    if n%2 == 0 and n//2 in d:
        d[n] = d[n//2] + 1
    if n not in d:
        temp = n
        intermediates = []
        while temp > 1:
            if temp%2 == 0:
                temp //= 2
            else:
                temp = 3 * temp + 1
            if temp in d:
                d[n] = res = d[temp] + len(intermediates) + 1
                for i, temp in enumerate(intermediates, 1):
                    d[temp] = res - i
                return res
            else:
                intermediates.append(temp)
    return d[n]

d={1: 1}
for k in range(first, last): function1(k, d)

print(time.clock() - start)