【问题标题】:List comprehension vs filter vs remove列表理解 vs 过滤 vs 删除
【发布时间】:2017-07-11 19:12:38
【问题描述】:

后两种方案比前一种方案好在哪里?

primes = (1, 2, 3, 5, 7)

# Classic solution
items = list(range(10))
for prime in primes:
    items.remove(prime)
items

# List comprehension
items = list(range(10))
[item for item in items if item not in primes]

# Filter
items = list(range(10))
list(filter(lambda item: item not in primes, items))

这三个例子是我在一本书中看到的,它说第一个解决方案需要 O(n*m) 时间 (n=len(items), m=len(primes)) 而后两个需要 O (n*1) time... 对第一个解决方案进行了 50 次比较(实际上稍微好一点 - 40 次比较),而后者只有 10 次。

我不明白这一点。我不明白它怎么会节省时间或内存。

这是书中解释这一点的段落:

要从列表中删除或插入单个项目,Python 需要复制 整个列表,对于较大的列表尤其重。执行时 这只是一次,当然不是那么糟糕。但是当执行一个大 删除的数量,过滤器或列表理解要快得多 解决方案,因为如果结构合理,它只需要复制列表 一次。 ....然后是例子... 对于大型项目列表,后两者要快得多。这是因为 操作要快得多。要使用 n=len(items) 和 m=len(primes) 进行比较, 第一个需要 O(m*n)=5*10=50 操作,而后两个需要 O(n*1)=10*1=10 次操作。

编辑: 书没有错。 primes = set((1, 2, 3, 5, 7)) 是正确的声明,而不是 primes = (1, 2, 3, 5, 7)

【问题讨论】:

  • 在纸上解决就明白了。我们可能无法真正使它比代码更清晰。
  • 所有解都是O(n*m),因为线性搜索长度为mn次的素数列表。
  • @itachi 我认为这本书试图说 IN 是测试存在性的有效运算符。后两个 sn-ps 可以利用 IN,因此如果素数是一个 Set,则具有 O(n)。
  • 你确定这本书没有将素数定义为一个集合吗?即primes = {1, 2, 3, 5, 7}?只有在这样的情况下,该陈述才会为真。
  • 这本书似乎暗示,由于primes 是一个简短的常量列表,它只是对 O() 有贡献的一个因素。另一方面,remove()O(n),所以你有一个隐藏的嵌套循环。其他两种解决方案只有一个直循环。

标签: python list data-structures filter list-comprehension


【解决方案1】:

如果书中的代码与您发布的完全相同,那么这本书完全错误

第一个例子有时间复杂度O(n*m),但其他两个也是。

如果primesset(或dict),那么这将是正确的——在哈希图中使用in 运算符进行存在查找具有时间复杂度O(1),但在listtupleO(n)!因此,O(n*m)的总复杂度。

让我们通过一些测量来检查一下:

t = tuple(range(10000))
l = list(t)
s = set(t)
d = {i:1 for i in l}

In [16]: %%timeit
4738 in t
   ....: 
10000 loops, best of 3: 45.5 µs per loop

In [17]: %%timeit
4738 in l
   ....: 
10000 loops, best of 3: 45.4 µs per loop

In [18]: %%timeit
4738 in s
   ....: 
10000000 loops, best of 3: 36.9 ns per loop

In [19]: %%timeit
4738 in d
   ....: 
10000000 loops, best of 3: 38 ns per loop

注意set 中的查找是~37ns(类似于dict),比list/tuple~45us 快3 个数量级。

【讨论】:

  • 添加dict到测试,应该是最快的
  • 不应该,因为set在内部使用dict,但我会添加它。
  • .remove() 约为 O(n),这就是 m*n 的来源
  • @hop,是的,但是另外两个也是O(n*m):对于n个项目中的每个项目,检查它是否存在于一个m的元组中(其中一个元组查找是O(m))。
  • @randomir:我没有说你错了,只是这似乎是示例的意图。
【解决方案2】:

主要问题来自items.remove(prime)。这是因为 Python 的列表是可变长度数组,而不是链表。他们使用一个连续的内存块来引用其他对象。如果从块中的任何位置插入/删除元素,则必须将块中的所有元素移动到新的连续内存块(在数组的开头或结尾插入一些优化)。 see the documentation here 您遍历列表len(primes) 次,对于每个删除,您遍历项目len(items) 次。如果该元素存在,则将项目列表复制到一个新的连续块中,不包括匹配的项目。将元素复制到新块存在隐藏成本。

另外两个示例遍历列表,保留所有当前元素。并根据提供的过滤器返回一个新列表。

【讨论】:

  • 不是我正在寻找的答案,而是 +1 的洞察力!学到了一些关于 python 数组的新知识:)
  • @QuoVadis:在您的链接之后,没有关于在开头插入元素的消息。这是一个 O(n) 操作。
  • 这取决于围绕初始同构块实施的优化。
  • @Daniel 我猜他的意思是摊销 O(1)
猜你喜欢
  • 2020-08-17
  • 1970-01-01
  • 1970-01-01
  • 2018-03-31
  • 2021-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-26
相关资源
最近更新 更多