有效地测试一个项目是否在一个排序的字符串列表中
Efficiently test if an item is in a sorted list of strings
假设我有一个短小写 [a-z] 字符串列表(最大长度 8):
L = ['cat', 'cod', 'dog', 'cab', ...]
如何有效地确定字符串 s
是否在此列表中?
我知道我可以 if s in L:
但我可以预排序 L
和二叉树搜索。
我什至可以一个字母一个字母地建造自己的树。所以设置s='cat'
:
所以 T[ ord(s[0])-ord('a') ]
给出了通往 'cat'
和 'cab'
等的子树。但是哎呀,乱七八糟的!
我也可以制作自己的 hashfunc,因为 L 是静态的。
def hash_(item):
w = [127**i * (ord(j)-ord('0')) for i,j in enumerate(item)]
return sum(w) % 123456
... 并且只是 fiddle 数字,直到我没有得到重复的数字。又丑了。
是否有任何开箱即用的东西我可以使用,还是我必须自己动手?
沿着 complexity/optimisation 曲线当然会有解决方案,所以如果这个问题过于开放,我提前道歉。
我正在寻找能够以低 LoC 成本换取体面性能增益的东西。
内置 Python set
几乎肯定是您可以使用的最高效的设备。 (当然,你可以推出可爱的东西,比如你的“词汇”的 DAG,但这会慢很多)。
因此,将您的 list
转换为 set
(如果要进行多项测试,最好构建一次)并测试成员资格:
s in set(L)
或者,对于多个测试:
set_values = set(L)
# ...
if s in set_values:
# ...
这里有一个简单的例子来说明性能:
from string import ascii_lowercase
import random
n = 1_000_000
L = [''.join(random.choices(ascii_lowercase, k=6)) for _ in range(n)]
搭建场景的时间:
%timeit set(L)
# 99.9 ms ± 49.5 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
查询集的时间:
set_values = set(L)
# non-existent string
%timeit 'foo' in set_values
# 45.1 ns ± 0.0418 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
# existing value
s = L[-1]
a = %timeit -o s in set_values
# 45 ns ± 0.0286 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
对比直接针对 list
进行测试:
b = %timeit -o s in L
# 16.5 ms ± 24.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
b.average / a.average
# 359141.74
您上次进行 350,000 倍加速是什么时候 ;-)?
假设我有一个短小写 [a-z] 字符串列表(最大长度 8):
L = ['cat', 'cod', 'dog', 'cab', ...]
如何有效地确定字符串 s
是否在此列表中?
我知道我可以 if s in L:
但我可以预排序 L
和二叉树搜索。
我什至可以一个字母一个字母地建造自己的树。所以设置s='cat'
:
所以 T[ ord(s[0])-ord('a') ]
给出了通往 'cat'
和 'cab'
等的子树。但是哎呀,乱七八糟的!
我也可以制作自己的 hashfunc,因为 L 是静态的。
def hash_(item):
w = [127**i * (ord(j)-ord('0')) for i,j in enumerate(item)]
return sum(w) % 123456
... 并且只是 fiddle 数字,直到我没有得到重复的数字。又丑了。
是否有任何开箱即用的东西我可以使用,还是我必须自己动手?
沿着 complexity/optimisation 曲线当然会有解决方案,所以如果这个问题过于开放,我提前道歉。
我正在寻找能够以低 LoC 成本换取体面性能增益的东西。
内置 Python set
几乎肯定是您可以使用的最高效的设备。 (当然,你可以推出可爱的东西,比如你的“词汇”的 DAG,但这会慢很多)。
因此,将您的 list
转换为 set
(如果要进行多项测试,最好构建一次)并测试成员资格:
s in set(L)
或者,对于多个测试:
set_values = set(L)
# ...
if s in set_values:
# ...
这里有一个简单的例子来说明性能:
from string import ascii_lowercase
import random
n = 1_000_000
L = [''.join(random.choices(ascii_lowercase, k=6)) for _ in range(n)]
搭建场景的时间:
%timeit set(L)
# 99.9 ms ± 49.5 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
查询集的时间:
set_values = set(L)
# non-existent string
%timeit 'foo' in set_values
# 45.1 ns ± 0.0418 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
# existing value
s = L[-1]
a = %timeit -o s in set_values
# 45 ns ± 0.0286 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
对比直接针对 list
进行测试:
b = %timeit -o s in L
# 16.5 ms ± 24.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
b.average / a.average
# 359141.74
您上次进行 350,000 倍加速是什么时候 ;-)?