【发布时间】:2019-12-30 20:16:24
【问题描述】:
我有一个字典“更新”算法,我怀疑它不是最有效的方法。当我运行我的程序并不断地向我现有的字典中添加一个新字典时,性能会随着时间的推移而显着降低。我想找到一种更高效的方式。
我的字典更新操作
我有一个循环,每次迭代都处理一个文件,并产生一个“字典列表的字典”。每个主dict键都有一个列表值,其中的项目本身就是dict,其中可以有多个。在此示例中,属于 B 的列表中有两个字典。我可能会处理第一个文件并得到这个结果:
{'A': [{'filename': 6311, 'id': 6634, 'num_transactions': 4969, 'total': 7808}],
'B': [{'filename': 6311, 'id': 3578, 'type': 8268, 'diameter': 2281, 'width': 4617},
{'filename': 6311, 'id': 2289, 'type': 1553, 'diameter': 4104, 'width': 8725}]}
然后我可能会处理另一个文件并得到这个:
{'C': [{'filename': 7775, 'id': 177, 'count': 6139, 'needed': 7905}],
'B': [{'filename': 7775, 'id': 7540, 'type': 9854, 'diameter': 3729, 'width': 9145},
{'filename': 7775, 'id': 27, 'type': 2380, 'diameter': 7209, 'width': 6023}]}
然后我将这些字典组合成一个主字典,在其中我根据它们的键值不断组合列表。上述两个字典的组合将导致(这里的顺序是任意的,但为了可读性而排序):
{'A': [{'filename': 6311, 'id': 6634, 'num_transactions': 4969, 'total': 7808}],
'B': [{'filename': 6311, 'id': 3578, 'type': 8268, 'diameter': 2281, 'width': 4617},
{'filename': 6311, 'id': 2289, 'type': 1553, 'diameter': 4104, 'width': 8725},
{'filename': 7775, 'id': 7540, 'type': 9854, 'diameter': 3729, 'width': 9145},
{'filename': 7775, 'id': 27, 'type': 2380, 'diameter': 7209, 'width': 6023}],
'C': [{'filename': 7775, 'id': 177, 'count': 6139, 'needed': 7905}]}
请注意,我必须有一个最终的 master_dict,其中包含我所有字典中的组合数据,这是不可协商的。
算法和性能
以下是生成随机cur_dicts 并不断将其结果添加到master_dict 的完整程序。函数add_to_master_dict() 代表我的更新算法。
import random
import timeit
import matplotlib.pyplot as plt
random.seed(0)
a_keys = ['id', 'num_transactions', 'total']
b_keys = ['id', 'type', 'diameter', 'width']
c_keys = ['id', 'count', 'needed']
key_dict = {'A':a_keys, 'B':b_keys, 'C':c_keys}
def generate_cur_dict(key_dict):
cur_dict = {}
filename_int = random.randint(0, 10000)
for main in random.sample(key_dict.keys(),
random.randint(1, len(key_dict.keys()))):
cur_dict[main] = []
num_rows = random.choice([1, 1, random.randint(1, 3)])
for _ in range(num_rows):
temp_dict = {}
temp_dict['filename'] = filename_int
for k in key_dict[main]:
temp_dict[k] = random.randint(0, 10000)
cur_dict[main].append(temp_dict)
return cur_dict
# Hacky use of variable scope by assuming existence of cur/master_dict,
# but easiest way to pass to timeit
def add_to_master_dict():
if not master_dict: # master_dict is empty
master_dict.update(cur_dict)
else:
for k in cur_dict.keys():
if k in master_dict:
# In case of None value rather than a list
if cur_dict[k] is None:
continue
else:
# Combine the two lists based on key
master_dict[k] = master_dict[k] + cur_dict[k]
else:
# If key not in master dict, just add the cur_dict value to the
# master_dict
master_dict[k] = cur_dict[k]
master_dict = {}
times = []
for i in range(50001):
cur_dict = generate_cur_dict(key_dict)
times.append(timeit.timeit(add_to_master_dict, number=1))
# Easy visual way to see how much it slows down over time
if i % 1000 == 0:
print(i)
plt.figure(figsize=(10, 6))
plt.plot(times)
我知道这不是使用 timeit 最优雅的方式 - 我没有取平均执行,所以有很多变化 - 但我只是想演示这个概念。应该清楚的是,如果您对任何大量迭代运行此程序,add_to_master_dict() 会陷入相当多的困境,因此我可能会在这里查看指数增长以进行更新。
关于如何以(希望)实现线性时间的方式执行更新操作有什么建议吗?我已经能够找到在简单情况下表现良好的 dict/list 更新算法,但对于我的 dict of lists 用例却一无所获。
【问题讨论】:
标签: python list performance dictionary