python集与元组查找。元组中的查找是否为O(1)?
最近我在这里看到一个关于python的问题 :。评论中有人回答说可以这样做:python集与元组查找。元组中的查找是否为O(1)?,python,Python,最近我在这里看到一个关于python的问题 :。评论中有人回答说可以这样做: 1 in(1,2,3)检查项目集合中是否存在1。但根据我的说法,{1,2,3}中的1应该快得多。正如您在讨论中所看到的,一些声誉很高的人继续说,()对于固定大小的输入更快。并且具有比{}更快的查找速度。我在这里问这个问题是因为我想知道哪一个是正确的,而且我不知道()是fiexd size还是variable size。我只是要求在原始问题中引用一下,这样如果我错了,我可以纠正自己,但是用户在清除我的计算机科学基础知识时
1 in(1,2,3)
检查项目集合中是否存在1。但根据我的说法,{1,2,3}中的1应该快得多。正如您在讨论中所看到的,一些声誉很高的人继续说,()
对于固定大小的输入更快。并且具有比{}
更快的查找速度。我在这里问这个问题是因为我想知道哪一个是正确的,而且我不知道()
是fiexd size
还是variable size
。我只是要求在原始问题中引用一下,这样如果我错了,我可以纠正自己,但是用户在清除我的计算机科学基础知识时,没有提到他的论点,即在元组中查找是O(1)
。所以我在这里问它。当你说类似O(n)
的话,你必须说出n
是什么。这里,n
是元组的长度。。。但元组不是一个输入。你不能把元组当作参数或任何东西n
在您链接的对话中始终是2
,或者对于示例元组是3
,因此对于这个特定的n
,O(n)
与O(2)
或O(1)
是一样的
正如您现在可能已经注意到的,当n
是一个常数时,谈论O(n)
没有多大意义。如果你有这样一个函数
def in_(element, tup):
return element in tup
您可以说运行时是O(n)
元素比较,其中n
是len(tup)
,但对于类似
usr in ('Y', 'y')
谈论n
不是很有用。当你说类似O(n)
的话时,你必须说出n
是什么。这里,n
是元组的长度。。。但元组不是一个输入。你不能把元组当作参数或任何东西n
在您链接的对话中始终是2
,或者对于示例元组是3
,因此对于这个特定的n
,O(n)
与O(2)
或O(1)
是一样的
正如您现在可能已经注意到的,当n
是一个常数时,谈论O(n)
没有多大意义。如果你有这样一个函数
def in_(element, tup):
return element in tup
您可以说运行时是O(n)
元素比较,其中n
是len(tup)
,但对于类似
usr in ('Y', 'y')
谈论n
不是很有用。尽管另一位评论者在技术上正确地认为中的x在运行时是O(1),但比较相同大小的集合和元组的性能仍然很有趣。讨论可以概括为考虑包含{,代码,x,{a,b,c,…} /代码>的表达式的不同程序。如果表达式由n
项组成,则在所有可能的此类程序中,n
可被视为big-O分析的输入。(如果仍然有人坚持认为可以在运行时提供不同的n
,只需想象该函数是使用exec
创建的)
这类表达式的性能问题是Python运行时必须构造一个一次性集,用于
测试中的,然后立即丢弃它。这在生成的部件中清晰可见:
>>> import dis
>>> def is_valid(x):
... return x in {1, 2, 3}
...
>>> dis.dis(is_valid)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 LOAD_CONST 2 (2)
9 LOAD_CONST 3 (3)
12 BUILD_SET 3
15 COMPARE_OP 6 (in)
18 RETURN_VALUE
构造一组n
元素显然至少要花费O(n)。换句话说,使用一个集合(作为文字常量实现)的测试是而不是O(1),因为解释器必须构造集合。这就是评论者试图通过提及建筑成本来理解的
事实上,它变得更奇怪;由于Python VM的性质,编译器可以在编译时构造仅由数字文本组成的元组,它可以:
>>> import dis
>>> def is_valid(x):
... return x in (1, 2, 3)
...
>>> dis.dis(is_valid)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 4 ((1, 2, 3))
6 COMPARE_OP 6 (in)
9 RETURN_VALUE
注意(1,2,3)
常量不需要逐项构建-这是因为它已经由编译器构建并插入到函数的环境中。因此,的这个实现是有效的
实际上可能比使用set的实现快!这很容易测试:
$ python -m timeit -s 'def is_valid(x): return x in {1, 2, 3}' 'is_valid(-1)'
10000000 loops, best of 3: 0.189 usec per loop
$ python -m timeit -s 'def is_valid(x): return x in (1, 2, 3)' 'is_valid(-1)'
10000000 loops, best of 3: 0.128 usec per loop
同样,另一位评论者是对的
增加集合/元组的大小并不会使平衡朝着有利于集合的方向倾斜——构造一组n
项,然后执行快速的常数时间搜索,总比在预先创建的元组上迭代查找项要昂贵得多。这是因为集合创建必须分配集合(可能多次)并计算所有项的哈希。虽然元组搜索和集合大小都是O(n),但集合的元组具有更大的常数因子
实现O(1)查找的正确方法需要手动实现编译器自动对元组进行的优化:
_valid = {1, 2, 3}
def is_valid(x):
return x in _valid
将此代码与使用元组的等效代码进行比较,即使项目数很少,集合也总是更快。随着项目数量的增加,集合通过其O(1)查找成为明显的赢家。尽管另一位评论者在技术上正确地认为
中的x在运行时是O(1),但比较相同大小的集合和元组的性能仍然很有趣。讨论可以概括为考虑包含{,代码,x,{a,b,c,…} /代码>的表达式的不同程序。如果表达式由n
项组成,则在所有可能的此类程序中,n
可被视为big-O分析的输入。(如果仍然有人坚持认为可以在运行时提供不同的n
,只需想象该函数是使用exec
创建的)
性能问题
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 3 (frozenset({'name', 'known_as'}))
6 COMPARE_OP 6 (in)
9 RETURN_VALUE
$ python3.5 -m timeit -s "def is_valid(x): return x in {'name', 'known_as'}" "is_valid('')"
10000000 loops, best of 3: 0.0815 usec per loop
$ python3.5 -m timeit -s "def is_valid(x): return x in ('name', 'known_as')" "is_valid('')"
10000000 loops, best of 3: 0.0997 usec per loop