python - 为什么不为 "in"操作设置文字 O(1)?

标签 python set tuples literals membership

用一个变量来测试很多常量是很常见的

if x in ('foo', 'bar', 'baz'):

而不是

if x == 'foo' or x == 'bar' or x == 'baz':

我见过很多“使用 {'foo', 'bar', 'baz'} 而不是 ('foo', 'bar', 'baz') 对于 O(1) 性能”,这是有道理的,但测试显示出非常奇怪的结果。

%timeit 1 in {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
27.6 ns ± 2.35 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit 10 in {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
136 ns ± 4.04 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit 0 in {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
186 ns ± 26.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

为什么查找集合文字不是常数时间?

最佳答案

嗯,这里有几件事。

  1. set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) super 慢,因为它可能首先构建列表。我想,3.7+ 中有一些优化,但无论如何。因此,设置文字速度更快。
  2. “检查第一个成员甚至有点慢”——关于集合的事情——它不是神奇的 O(1)。集合成员检查是散列+取模+散列的比较+冲突/删除的回退。没有所谓的“第一个成员”。
  3. 元组在小数据上的表现优于集合——因为集合利用了很多机制。它是 O(1),但常数在某些范围内高于 O(N) 的值。使用 10**6-length 分析您的代码,您会看到不同之处
  4. 用文字计时是个奇怪的想法,通常快速成员检查利用已经创建的容器:

    t = tuple(range(10**6))
    s = set(range(10**6))
    %timeit 999999 in t
    11.9 ms ± 92 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit 999999 in s
    52 ns ± 0.538 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    

关于测试渐近复杂性的旁注——您应该始终检查增长的幅度,原始数据毫无意义。即

x = 1; t = tuple(range(10**x)); s = set(range(10**x))
%timeit (-1) in t
168 ns ± 22.9 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit (-1) in s
38.3 ns ± 0.46 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

x = 2; t = tuple(range(10**x)); s = set(range(10**x))
%timeit (-1) in t
1.1 µs ± 17.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit (-1) in s
37.7 ns ± 0.101 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

x = 4; t = tuple(range(10**x)); s = set(range(10**x))
%timeit (-1) in t
107 µs ± 860 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit (-1) in s
39 ns ± 1.66 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

x = 6; t = tuple(range(10**x)); s = set(range(10**x))
%timeit (-1) in t
10.8 ms ± 114 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit (-1) in s
38 ns ± 0.333 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

所以你可以在这里清楚地看到什么是线性与常数。

关于python - 为什么不为 "in"操作设置文字 O(1)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54640644/

相关文章:

javascript - JavaScript 中的 Set 是否保证稳定?

c++ - 如何将函数应用于可变参数列表并将它们分类为元组

python - 如何在 Python 中创建递增的文件名?

python - 如何在seaborn对象api中定义带有rgb值的颜色?

python - 如何将 Q-learning 应用于每个时间步执行多个操作的 OpenAI-gym 环境?

Python - 如果字符串包含列表或集合中的单词

Python:如何解释 np.argmax() 的结果?

data-structures - 什么是无序并允许重复调用的数据结构?

python - 如何将树形元组转换为矩阵形元组?

haskell - 无法将预期类型 (Int -> Int -> Int) 与实际类型 `(t0, t1, t2)' 匹配