Python 究竟是如何检查列表的?

Posted

技术标签:

【中文标题】Python 究竟是如何检查列表的?【英文标题】:How exactly does Python check through a list? 【发布时间】:2018-05-06 07:17:12 【问题描述】:

我正在为 python 做一个关于 codeacademy 的课程练习,我有几个问题我似乎找不到答案:

对于这段代码,python 究竟是如何检查某个东西是“在”列表中还是“不在”列表中?它是遍历列表中的每个项目来检查还是使用更快的过程?

此外,如果运行大量数字(数千或数百万)列表,该代码会受到怎样的影响?它会随着列表大小的增加而变慢吗?还有更好的选择吗?

numbers = [1, 1, 2, 3, 5, 8, 13]

def remove_duplicates(list):
  new_list = []
  for i in list: 
    if i not in new_list:
      new_list.append(i)
  return new_list

remove_duplicates(numbers)

谢谢!

附:为什么这段代码的功能不一样?

numbers = [1, 1, 2, 3, 5, 8, 13]

def remove_duplicates(list):
  new_list = []
  new_list.append(i for i in list if i not in new_list)
  return new_list

【问题讨论】:

append 更改为extend 即可回答您的第二个问题。 顺便说一下list(set(numbers)) 也删除了重复项 顺便说一句,在 Stack Exchange 网站上,最好每个问题问一个问题。这使您的问题对未来的读者更有用。但我想这里没问题,因为这两个问题是相关的。 在保持顺序的同时更快:***.com/questions/480214/… wiki.python.org/moin/TimeComplexity 【参考方案1】:

为了执行i not in new_list,Python 必须对列表进行线性扫描。一旦知道测试结果,扫描循环就会中断,但如果i 实际上不在列表中,则必须扫描整个列表以确定这一点。它以 C 的速度执行此操作,因此它比执行 Python 循环来显式检查每个项目要快。偶尔进行in some_list 测试是可以的,但如果您需要进行大量此类成员资格测试,最好使用set

平均而言,对于随机数据,测试成员必须扫描一半的列表项,并且通常执行扫描所花费的时间与列表的长度成正比。在通常的表示法中,列表的大小用n 表示,这个任务的时间复杂度写成 O(n)。

相比之下,确定set(或dict)的成员资格(平均而言)可以在恒定时间内完成,因此其时间复杂度为 O(1)。有关此主题的更多详细信息,请参阅 Python Wiki 中的 TimeComplexity。谢谢 Serge,提供那个链接。

当然,如果您使用set,那么您可以免费获得重复数据删除,因为不可能将重复的项目添加到集合中。

集合的一个问题是它们通常不保持顺序。但是您可以使用集合作为辅助集合来加速重复数据删除。这是对列表或其他有序集合进行重复数据删除的一种常用技术的说明,该技术确实保留了顺序。我将使用字符串作为数据源,因为我懒得输入列表。 ;)

new_list = []
seen = set()
for c in "this is a test":
    if c not in seen:
        new_list.append(c)
        seen.add(c)
print(new_list)

输出

['t', 'h', 'i', 's', ' ', 'a', 'e']

请参阅How do you remove duplicates from a list whilst preserving order? 了解更多示例。感谢 Jean-François Fabre 提供链接。


至于您的 PS,该代码将单个生成器对象附加到 new_list,它不会附加生成器将产生的内容。

我假设您已经尝试通过列表理解来做到这一点:

new_list = [i for i in list if i not in new_list]

这是行不通的,因为 new_list 在列表组合完成运行之前不存在,所以这样做 in new_list 会引发 NameError。即使你在列表组合之前做了new_list = [],它也不会被列表组合修改,列表组合的结果只会用一个新的列表对象替换那个空的列表对象。


顺便说一句,请不要使用 list 作为变量名(即使在示例代码中),因为这会遮蔽内置的 list 类型,这可能会导致神秘的错误消息。

【讨论】:

小细节:set 不会保留顺序。 你可以链接到那个帖子然后:***.com/questions/480214/… 好主意,@Serge! 感谢您的帮助!这彻底回答了我的问题! @PM2Ring 那是我没有意识到你只能选择一个“复选标记”【参考方案2】:

您问了多个问题,其中一个问您是否可以更有效地做到这一点。我会回答的。

好吧,假设您有数千或数百万个数字。具体从哪里来?假设它们存储在某种 txt 文件中,那么您可能想要使用 numpy(如果您坚持使用 Python)。示例:

import numpy as np

numbers = np.array([1, 1, 2, 3, 5, 8, 13], dtype=np.int32)
numbers = np.unique(numbers).tolist()

这将比使用 python 读取它并执行 list(set..) 更有效(最重要的是内存效率比较)

numbers = [1, 1, 2, 3, 5, 8, 13]
numbers = list(set(numbers))

【讨论】:

【参考方案3】:

您要求的是此函数的算法复杂性。要发现您需要查看每个步骤中发生的情况。

您一次扫描一个列表,这需要 1 个工作单元。这是因为从列表中检索的东西是O(1)。如果您知道索引,则可以在 1 次操作中检索它。

您要添加它的列表在最坏的情况下一次增加 1。因此,在任何时间点,unique 项目列表的大小都将是 n

现在,将您选择的项目添加到unique 项目列表中,在最坏的情况下将需要 n 项工作。因为我们必须扫描每个项目才能做出决定。

因此,如果您总结每个步骤的总工作量,它将是1 + 2 + 3 + 4 + 5 + ... n,即n (n + 1) / 2。因此,如果您有一百万个项目,您可以通过在公式中应用 n = million 来找到它。


这并不完全正确,因为list 的工作原理。但从理论上讲,以这种方式可视化会有所帮助。

【讨论】:

【参考方案4】:

回答标题中的问题:python 具有更有效的数据类型,但 list() 对象只是一个普通数组,如果您想要一种更有效的方法来搜索值,您可以使用 dict() 它使用哈希存储的对象以将其插入树中,我认为这是您在提到“更快的过程”时所想的。

关于第二个代码sn-p: list().append() 将你给它的任何值插入到列表的末尾,i for i in list if i not in new_list 是一个生成器对象,它将该生成器作为一个对象插入到数组中,list().extend() 做你想做的事:它接受一个迭代并追加它的所有元素到列表中

【讨论】:

以上是关于Python 究竟是如何检查列表的?的主要内容,如果未能解决你的问题,请参考以下文章

设置转换的列表的时间复杂度是多少?

Cocos2D 的可用文件后缀究竟是啥,用于多分辨率支持?

Django 内容类型究竟是如何工作的?

Json Web Token (JWT) 究竟是如何减少人在循环攻击的?

挖矿之王!Coinhive幕后操纵者究竟是何方神圣?

C 函数中的变量参数列表 - 如何正确遍历 arg 列表?