如何使用 Numpy 构建的集合字典提高性能?
How to improve performance with this dictionary of sets built with Numpy?
假设我们有 1000 万只袜子,每只袜子都有颜色 sockcolor[i]
并存储在 drawer[i]
中。
我想数一数这 20 个抽屉中每个抽屉有多少种不同种颜色。
为此,我使用了包含集合的字典(集合在底层使用哈希表,可以方便地计算唯一元素)。
以下代码有效,但速度很慢(10 百万只袜子大约需要 10 秒)。
我们如何使用 Numpy 技术(矢量化?)或避免 for
循环来加速此计算?
import numpy as np
N = 10*1000*1000
drawer = np.random.randint(0, 20, N) # 20 drawers
sockcolor = np.random.randint(0, 100*1000, N) # 100k different colors
d = {}
for i, k in enumerate(drawer):
if k not in d: # can be simplified with collections.defaultdict but no gain here
d[k] = set()
d[k].add(sockcolor[i])
for k, s in d.items():
print(k, len(s))
输出:
16 99323
7 99330
0 99339
17 99364
14 99272
12 99303
11 99334
6 99362
19 99287
10 99282
4 99280
18 99325
3 99316
15 99303
5 99280
13 99357
2 99328
9 99290
1 99293
8 99293
你的速度慢是因为没有使用序列的内置功能。仅遍历单个袜子一次。相反,将袜子颜色(不是单个袜子的索引)分配给抽屉。然后从每个抽屉的内容做一组:一次批发操作,而不是增量操作 set.add
,这对您的目的来说相对较慢。
你基本上已经有了从抽屉到袜子颜色的映射,但它们是随机的,你想按抽屉编号组织它们。
最简单的方法是先按抽屉编号对它们进行排序:
drawer_sort = np.argsort(drawer)
drawer = drawer[drawer_sort]
sockcolor = sockcolor[drawer_sort]
现在它们已经排序了,不需要查找抽屉号重复项,你只需要找到抽屉号变化的索引,形成范围,就是这些:
changes, = np.where(drawer[1:]-drawer[:-1])
starts = np.concatenate([[0], changes+1])
ends = np.concatenate([changes, [len(drawer)]])
现在您可以创建字典了:
result = {drawer[start]: sockcolor[start:end] for start, end in zip(starts, ends)}
这样,在 Python 中唯一完成的迭代是最后一行,如果有少量不同的 drawer
值(在您的情况下不超过 20 个),这将非常快).
结果仍然可以有重复的 sockcolor
值,但这在 numpy 中很容易解决:
result = {drawer: np.unique(sockcolors) for drawer, sockcolors in result.items()}
假设我们有 1000 万只袜子,每只袜子都有颜色 sockcolor[i]
并存储在 drawer[i]
中。
我想数一数这 20 个抽屉中每个抽屉有多少种不同种颜色。
为此,我使用了包含集合的字典(集合在底层使用哈希表,可以方便地计算唯一元素)。
以下代码有效,但速度很慢(10 百万只袜子大约需要 10 秒)。
我们如何使用 Numpy 技术(矢量化?)或避免 for
循环来加速此计算?
import numpy as np
N = 10*1000*1000
drawer = np.random.randint(0, 20, N) # 20 drawers
sockcolor = np.random.randint(0, 100*1000, N) # 100k different colors
d = {}
for i, k in enumerate(drawer):
if k not in d: # can be simplified with collections.defaultdict but no gain here
d[k] = set()
d[k].add(sockcolor[i])
for k, s in d.items():
print(k, len(s))
输出:
16 99323
7 99330
0 99339
17 99364
14 99272
12 99303
11 99334
6 99362
19 99287
10 99282
4 99280
18 99325
3 99316
15 99303
5 99280
13 99357
2 99328
9 99290
1 99293
8 99293
你的速度慢是因为没有使用序列的内置功能。仅遍历单个袜子一次。相反,将袜子颜色(不是单个袜子的索引)分配给抽屉。然后从每个抽屉的内容做一组:一次批发操作,而不是增量操作 set.add
,这对您的目的来说相对较慢。
你基本上已经有了从抽屉到袜子颜色的映射,但它们是随机的,你想按抽屉编号组织它们。
最简单的方法是先按抽屉编号对它们进行排序:
drawer_sort = np.argsort(drawer)
drawer = drawer[drawer_sort]
sockcolor = sockcolor[drawer_sort]
现在它们已经排序了,不需要查找抽屉号重复项,你只需要找到抽屉号变化的索引,形成范围,就是这些:
changes, = np.where(drawer[1:]-drawer[:-1])
starts = np.concatenate([[0], changes+1])
ends = np.concatenate([changes, [len(drawer)]])
现在您可以创建字典了:
result = {drawer[start]: sockcolor[start:end] for start, end in zip(starts, ends)}
这样,在 Python 中唯一完成的迭代是最后一行,如果有少量不同的 drawer
值(在您的情况下不超过 20 个),这将非常快).
结果仍然可以有重复的 sockcolor
值,但这在 numpy 中很容易解决:
result = {drawer: np.unique(sockcolors) for drawer, sockcolors in result.items()}