如何使python for循环更快



我有一个字典列表,如下所示:

[{'user': '123456', 'db': 'db1', 'size': '8628'}
{'user': '123456', 'db': 'db1', 'size': '7168'}
{'user': '123456', 'db': 'db1', 'size': '38160'}
{'user': '222345', 'db': 'db3', 'size': '8628'}
{'user': '222345', 'db': 'db3', 'size': '8628'}
{'user': '222345', 'db': 'db5', 'size': '840'}
{'user': '34521', 'db': 'db6', 'size': '12288'}
{'user': '34521', 'db': 'db6', 'size': '476'}
{'user': '2345156', 'db': 'db7', 'size': '5120'}.....]

此列表包含数百万个条目。每个用户都可以在多个数据库中找到,每个用户在同一个数据库中可以有多个实体。我想总结一下每个用户,每个数据库占用的大小是多少。我不想用熊猫。目前我是这样做的:

  • 我创建了两个唯一用户和唯一dbs的列表
  • 使用这些列表来遍历大列表,并总结用户和数据库相同的地方
result = []
for user in unique_users:
for db in unique_dbs:
total_size = 0
for i in big_list:
if (i['user'] == user and i['db'] == db):
total_size += float(i['size'])
if(total_size) > 0:
row = {}
row['user'] = user
row['db'] = db
row['size'] = total_size
result.append(row)

问题是,这个三重循环发展成了非常大的东西(数千亿次迭代(,需要很长时间才能总结结果。如果big_list很小,则这一操作非常有效。

我应该如何处理才能保持它的快速和简单?非常感谢!

当前方法存在两个主要问题:低效算法高效数据结构

首先,所使用的算法显然效率低下,因为它在大列表上迭代了很多次。不需要对整个列表进行迭代来过滤唯一的用户和数据库。您可以对大列表进行一次迭代,并使用字典聚合数据。目标字典的关键字只是一个(user, db)元组。字典的值为total_size。下面是一个未经测试的例子:

# Aggregation part
# Note: a default dict can be used instead to make the code possibly simpler
aggregate_dict = dict()
for i in big_list:
key = (i['user'], i['db'])
value = float(i['size'])
if key in aggregate_dict:
aggregate_dict[key] += value
else:
aggregate_dict[key] = value
# Fast creation of `result`
result = []
for user in unique_users:
for db in unique_dbs:
total_size = aggregate_dict.get((user, key))
if total_size is not None and total_size > 0:
result.append({'user': user, 'db': db, 'size': total_size})

另一个问题是低效的数据结构:对于每一行,都会复制键,而可以使用元组。事实上,更好的数据结构是存储(column, items)键值的字典,其中items是目标列的项目列表。这种存储数据的方式被称为数据帧。这大致是Pandas内部使用的(除了它是一个Numpy数组,它甚至更好,因为它比大多数操作的列表更紧凑,通常更高效(。将此数据结构用于输入和输出应该会显著提高速度(如果与Numpy结合使用(,并降低内存占用

尝试将用户到数据库映射到字典中的总大小。它将需要额外的内存,但访问&只需要通过一次数据:

user_to_db_to_size = {}
for entry in unique_users:
user = entry['user']
db = entry['db']
size = int(entry['size'])
if user not in user_to_db_to_size:
user_to_db_to_size[user] = {}
if db not in user_to_db_to_size[user]:
user_to_db_to_size[user][db] = 0
user_to_db_to_size[user][db] += size
print(user_to_db_to_size)

对于您的样本数据,它会生成:

{'123456': {'db1': 53956}, '222345': {'db3': 17256, 'db5': 840}, '34521': {'db6': 12764}, '2345156': {'db7': 5120}}

现在,您可以使用访问每个用户/db的总大小

print(user_to_db_to_size['123456']['db1'])  # 53956

总之,看看这个问题的三个建议答案,rdas的方法可能是赢家。经过几次修改,它比Jérôme的解决方案快57%,比原始代码快180倍。Сергей的解在经过大量修剪的结果子集(1000个条目(上慢了大约350倍;它的规模似乎也很糟糕,我没有时间等待完整数据集的结果。

时间安排如下:

180.4580695623077Jérôme
方法 时间 相对
原始 102.873720700000413
Jérôme(更新(0.90002190001541751.5793526426731528
rdas0.68661309999879451.2048642525015463
rdas(更新(0.658064629999911411.1547354157572032
rdas with defaultdict0.56986759998835621.0

如果使用Counter并将值对的元组(user,db(作为键,则:

from collections import Counter
data = [{'user': '123456', 'db': 'db1', 'size': '8628'},
{'user': '123456', 'db': 'db1', 'size': '7168'},
{'user': '123456', 'db': 'db1', 'size': '38160'},
{'user': '222345', 'db': 'db3', 'size': '8628'},
{'user': '222345', 'db': 'db3', 'size': '8628'},
{'user': '222345', 'db': 'db5', 'size': '840'},
{'user': '34521', 'db': 'db6', 'size': '12288'},
{'user': '34521', 'db': 'db6', 'size': '476'},
{'user': '2345156', 'db': 'db7', 'size': '5120'}]
print(sum((Counter({(d['user'], d['db']): int(d['size'])}) for d in data), start=Counter()))
Counter({('123456', 'db1'): 53956, ('222345', 'db3'): 17256, ('34521', 'db6'): 12764, ('2345156', 'db7'): 5120, ('222345', 'db5'): 840})

最新更新