我通过找到值x出现的范围来计算在排序列表a中值x的出现次数:
from bisect import bisect_left, bisect_right
def count(a, x):
start = bisect_left(a, x)
stop = bisect_right(a, x)
return stop - start
但是,嘿,事情还没开始,我们可以通过省略开始之前的部分来优化第二次搜索(文档):
def count(a, x):
start = bisect_left(a, x)
stop = bisect_right(a, x, start)
return stop - start
但是当我进行基准测试时,优化后的版本花费的时间超过了两倍:
254 ms ± 1 ms original
525 ms ± 2 ms optimized
为什么?
这个基准测试会生成一个从0到99999的一千万个随机整数的已排序列表,然后计算所有不同的整数(只是为了基准测试,不需要指出Counter
)(在线试用!):
import random
from bisect import bisect_left, bisect_right
from timeit import repeat
from statistics import mean, stdev
def original(a, x):
start = bisect_left(a, x)
stop = bisect_right(a, x)
return stop - start
def optimized(a, x):
start = bisect_left(a, x)
stop = bisect_right(a, x, start)
return stop - start
a = sorted(random.choices(range(100_000), k=10_000_000))
unique = set(a)
def count_all():
for x in unique:
count(a, x)
for count in original, optimized:
times = repeat(count_all, number=1)
ts = [t * 1e3 for t in sorted(times)[:3]]
print(f'{round(mean(ts)):4} ms ± {round(stdev(ts)):2} ms ', count.__name__)
int
是相同的对象,并且它们一次性按顺序生成(因此它们在内存中很可能是连续的)。你所要做的就是将range(100_000)
改为tuple(range(100_000)
。这样做可以将“原始”提高约 34%,将“优化”提高约 61%。减少缓存的影响更能改善优化,这意味着它实际上是一个缓存未命中问题。 - ShadowRanger