Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/powerbi/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 字典搜寻及;插入优化_Python - Fatal编程技术网

Python 字典搜寻及;插入优化

Python 字典搜寻及;插入优化,python,Python,当使用字典时,我遇到了严重的速度减慢,因为字典会增加到几千个键 我正在处理一个包含1000000行数据的文件,我正在使用字典构建一个类似于图形的数据结构 这是我的瓶颈函数 def create_edge(node_a, node_b, graph): if node_a not in graph.keys(): graph[node_a] = {node_b: 1} elif node_b in graph[node_a].keys(): grap

当使用字典时,我遇到了严重的速度减慢,因为字典会增加到几千个键

我正在处理一个包含1000000行数据的文件,我正在使用字典构建一个类似于图形的数据结构

这是我的瓶颈函数

def create_edge(node_a, node_b, graph):
    if node_a not in graph.keys():
        graph[node_a] = {node_b: 1}
    elif node_b in graph[node_a].keys():
        graph[node_a][node_b] += 1
    else:
        graph[node_a][node_b] = 1
create_edge
将从
node_a
node_b
创建和设置边,或者将它们之间已经存在的边的权重加1

由于我的节点是由一个字符串唯一id标识的,所以我使用字典来存储,假设如果存在一个键,则搜索和插入平均需要O(1)

如果我把
create_edge
注释掉,我可以每秒处理大约20000条记录,而
create_edge
作为我的管道的一部分,大约每秒处理20条记录

前100条记录的处理大约需要500毫秒。 当字典大小增加到10000左右时—处理100条记录大约需要15000ms,每个记录进程平均调用
create\u edge
大约4次—因此当字典大小为10000时,400次调用
create\u edge
需要15秒

首先,这些运行时有意义吗?对我来说似乎很重要,如果我错了就纠正我

第二,为更好地运行时间而优化词典使用的建议将不胜感激

我希望字典大小至少为100000,以完成整个1000000条记录的处理


编辑:结论

你在钱上是对的,在这里犯了两个noob错误:)

keys()
调用极大地增加了复杂性,将每次边插入的时间从常量时间变为多边形时间(平方),将图中的
if节点替换为图中的
if节点。keys()
将在~300ms内生成100条记录的常量处理时间

第二个错误是virtualenv配置,这让我相信我在使用python3,而实际上我在使用python2

python3do将keys()代码优化为一个常量时间搜索,这对运行时有好处,但对正确的代码样式没有好处

非常感谢你的帮助。
在删除
键()
调用后,我执行了一次运行时比较

# graph = {}
python version: 3.6.3
start time 11:44:56
Number of records: 1029493
graph created, 1231630 nodes
end time 11:50:35
total ~05:39

# graph = defaultdict(lambda : defaultdict(int))
python version: 3.6.3
start time 11:54:52
Number of records: 1029493
graph created, 1231630 nodes
end time  12:00:34
total ~05:42

# graph = {}
python version: 2.7.10
start time 12:03:25
Number of records: 1029493
graph created, 1231630 nodes
end time 12:09:40
total ~06:15

当测试
dict
中是否存在密钥时,只需使用
key in d
,而不是
key in d.keys()
。提取密钥以测试成员资格首先否定了使用
dict
的好处

请尝试以下操作:

def create_edge(node_a, node_b, graph):
    if node_a not in graph:
        graph[node_a] = {node_b: 1}
    elif node_b in graph[node_a]:
        graph[node_a][node_b] += 1
    else:
        graph[node_a][node_b] = 1
请注意,根本不调用
keys()
。这应该比你现在做的要快得多


请注意,在Python2中,
keys()
检查将比Python3慢得多,因为在Python2中,
keys()
创建了一个完整密钥集的列表。它在Python3中的工作方式不同,但即使在Python3中,不使用
keys()
,直接检查成员身份也会更快。

您不能只使用带有defaultdict(int)的defaultdict,如下所示:

返回:

defaultdict(<function __main__.<lambda>>,
            {'a': defaultdict(int, {'b': 2, 'c': 1})})
# equal to: {'a': {'b': 2, 'c': 1}}
defaultdict(,
{'a':defaultdict(int,{'b':2,'c':1})
#等于:{'a':{'b':2,'c':1}

我尝试了几种方法,这似乎是一种有效的方法。此方法首先使用计数器统计所有事件,然后构建字典。感谢@Stefan Pochmann提供基准脚本。我用的一个是从

我正在使用Python3.6,结果在我的计算机上进行了测试

from timeit import timeit
from collections import defaultdict, Counter
from random import shuffle
from itertools import product

def f():   # OP's method modified with Tom Karzes' answer above.
    d = {}
    for i, j in edges:
        if i not in d:
            d[i] = {j: 1}
        elif j in d[i]:
            d[i][j] += 1
        else:
            d[i][j] = 1

def count_first(): 
    d = dict()
    for (v, w), c in Counter(edges).items():
        if v not in d:
            d[v] = {w: c}
        else:
            d[v][w] = c
    # Alternatively, (Thanks to Jean-François Fabre to point it out.)
    # d = defaultdict(lambda : defaultdict(int)) 
    # for (v, w), c in Counter(edges).items(): 
    #     d[v][w] = c

edges = list(product(range(300), repeat=2)) * 10
shuffle(edges)

# %timeit f()
270 ms ± 23.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# %timeit count_first()
180 ms ± 15.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
声明人:然而,我从ideone.com上得到的
count\u first()
的结果比OP的答案
f()
要慢


Stefan Pochmann做了一个基准测试来比较Python2和Python3中的不同方法。可以找到他在Python 2中的结果。对于Python3,请选中。感谢他并感谢他的代码审查。

这是什么Python版本?您是否有机会使用Python 2?因为
.keys()
获取的是键列表,而不是字典。因此,在
create_edge
中,您将执行两次
O(n)
操作,从转换为列表的整个词典中搜索两次。如果图中的节点a
,则需要执行
,请参阅。注意,如下面的注释所述,这仅适用于使用Python 2,而不是3。@SpencerWieczorek这是我怀疑的,因为它是一个著名的Python反模式,但在Python 3中,它返回一个带有O(1)成员身份测试的键视图。@AlanSTACK不,它不返回迭代器,尽管
dict_keys
对象是可编辑的。不相信我?尝试
next({}.keys())
关于keys()部分的解释很好,但是如果没有defaultdict/Counter,它仍然会很慢。@Jean-Françoisfare A
defaultdict
会更快,但代码中最大的问题是它使用的是
keys()
,特别是在OP使用Python 2的情况下。使用
defaultdict
最多可以避免冗余的密钥检查,从而将速度提高三倍,但它的优点是它可以消除错误调用
keys()
的可能性。@Jean-Françoisfare请出示支持该声明的基准。@Jean-Françoisfare Mine不同意,defaultdict+计数器占用的时间几乎是OP(固定)方式的两倍:@StefanPochmann有趣。我猜
defaultdict
的效率比我想象的要低。这实际上比OP快吗(当然是在修复了他们的
.keys()
错误之后)。你能分享你的基准测试结果吗?因为我不是坐在数据集上,我想OP可以做到。@StefanPochmann它可能会快得多。上下文切换(调用
create\u edge
)非常昂贵。@PatrickHaugh我看不出有什么大的区别,看看下面的时间:。这就是用这个函数添加边。如果我是内联的,OP的方式在我的测试中要快得多:我一定会尝试使用lambdas和defaultdict并发布结果,谢谢!我的速度稍微快一点
from timeit import timeit
from collections import defaultdict, Counter
from random import shuffle
from itertools import product

def f():   # OP's method modified with Tom Karzes' answer above.
    d = {}
    for i, j in edges:
        if i not in d:
            d[i] = {j: 1}
        elif j in d[i]:
            d[i][j] += 1
        else:
            d[i][j] = 1

def count_first(): 
    d = dict()
    for (v, w), c in Counter(edges).items():
        if v not in d:
            d[v] = {w: c}
        else:
            d[v][w] = c
    # Alternatively, (Thanks to Jean-François Fabre to point it out.)
    # d = defaultdict(lambda : defaultdict(int)) 
    # for (v, w), c in Counter(edges).items(): 
    #     d[v][w] = c

edges = list(product(range(300), repeat=2)) * 10
shuffle(edges)

# %timeit f()
270 ms ± 23.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# %timeit count_first()
180 ms ± 15.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)