这里有一个 numpy 解决方案:
import numpy as np
a = np.random.randint(0, 1000, 1000000)
index = np.argsort(a, kind='mergesort')
as_ = a[index]
jumps = np.r_[0, 1 + np.where(np.diff(as_) != 0)[0]]
result = {k: v for k, v in zip(as_[jumps], np.split(index, jumps[1:]))}
基准
numpy 以不太大的n 获胜;因为它使用 O(n log n) 排序算法,所以边距很小(pp2 是一种变体,它用快速排序代替了缓慢但稳定的合并排序,但代价是必须在之后对单个索引列表进行排序,pp3 将完整排序替换为argpartition 如果唯一元素的数量与元素的数量相比,这会提高一些速度。):
原始数组中有10个不同的整数值:
原始数组中的 100 个不同的整数值:
基准代码供参考:
import numpy as np
from collections import defaultdict
import perfplot
def pp(a):
index = np.argsort(a, kind='mergesort')
as_ = a[index]
jumps = np.r_[0, 1 + np.where(np.diff(as_) != 0)[0]]
pp_out = {k: v for k, v in zip(as_[jumps], np.split(index, jumps[1:]))}
return pp_out
def pp2(a):
index = np.argsort(a)
as_ = a[index]
jumps = np.r_[0, 1 + np.where(np.diff(as_) != 0)[0]]
pp_out = {k: np.sort(v)
for k, v in zip(as_[jumps], np.split(index, jumps[1:]))}
return pp_out
def Denziloe_JFFabre(a):
df_out = {v: [i for i, x in enumerate(a) if x == v] for v in set(a)}
return df_out
def FCouzo(a):
fc_out = defaultdict(list)
for i, elem in enumerate(a):
fc_out[elem].append(i)
return fc_out
def KKSingh(a):
kks_out = defaultdict(list)
list(map(lambda x: kks_out[x[0]].append(x[1]), zip(a, range(len(a)))))
return kks_out
def TMcDonaldJensen(a):
mdj_out = defaultdict(list)
for i, elem in enumerate(a):
mdj_out[elem].append(i)
return mdj_out
def RomanPerekhrest(a):
rp_out = {}
for k, m in enumerate(a):
rp_out.setdefault(m, []).append(k)
return rp_out
def SchloemerHist(a):
np.histogram(a, bins=np.arange(min(a), max(a)+2))
return
def SchloemerWhere(a):
out = {v: np.where(v == a)[0] for v in set(a)}
return out
perfplot.show(
setup=lambda n: np.random.randint(0, 10, n),
kernels=[
pp, pp2, Denziloe_JFFabre, FCouzo, KKSingh,
TMcDonaldJensen, RomanPerekhrest, SchloemerHist, SchloemerWhere
],
n_range=[2**k for k in range(19)],
xlabel='len(a)',
logx=True,
logy=True,
)