如何使用列表理解从列表中删除重复项? [复制]
Posted
技术标签:
【中文标题】如何使用列表理解从列表中删除重复项? [复制]【英文标题】:How to remove duplicate items from a list using list comprehension? [duplicate] 【发布时间】:2012-05-19 22:21:07 【问题描述】:如何使用列表推导从列表中删除重复项?我有以下代码:
a = [1, 2, 3, 3, 5, 9, 6, 2, 8, 5, 2, 3, 5, 7, 3, 5, 8]
b = []
b = [item for item in a if item not in b]
但它不起作用,只会产生相同的列表。为什么它会产生一个相同的列表?
【问题讨论】:
因为在您执行if item not in b
时b
是空的。列表推导在内存中完成,最后将结果分配给b
。
这意味着列表理解不像循环那样工作?
如果您不想使用集合,因为您想保留顺序,请查看itertools recipes 中的unique_everseen
迭代器。像这样使用:b = list(unique_everseen(a))
这是一种循环,但它会一次性生成结果……这也不足为奇。每当您有表达式x = y
时,首先评估y
,然后将结果分配给x
。但是在评估y
时,x
不会被修改。如果换成b = list(item for item in a if item not in b)
,你会有同样的疑问吗?
【参考方案1】:
它生成的列表与b
在运行时不包含任何元素相同。
你想要什么:
>>> a = [1, 2, 3, 3, 5, 9, 6, 2, 8, 5, 2, 3, 5, 7, 3, 5, 8]
>>> b = []
>>> [b.append(item) for item in a if item not in b]
[None, None, None, None, None, None, None, None]
>>> b
[1, 2, 3, 5, 9, 6, 8, 7]
【讨论】:
小心使用list comprehensions for side effects。请改用常规的 for 循环。 这也是一个O(n²)
答案,其中,对于可散列的输入,O(n)
是可能的(with 或 without 保留顺序),对于不可散列但可排序的输入,@987654328 @ 是可能的(尽管它用排序排序替换了原始排序,除非你努力用它们的索引装饰和取消装饰输入并将其合并到排序和重复数据删除中,以便第二次排序可以恢复原始排序)。【参考方案2】:
如果您不介意使用与列表理解不同的技术,您可以使用一个集合:
>>> a = [1, 2, 3, 3, 5, 9, 6, 2, 8, 5, 2, 3, 5, 7, 3, 5, 8]
>>> b = list(set(a))
>>> print b
[1, 2, 3, 5, 6, 7, 8, 9]
【讨论】:
我看过set函数,只是想知道上面的代码有什么问题,是否可以纠正? set 不会保持初始顺序...所以请注意这一点 @AdiRoiban:那个can be fixed with minimal code changes。它比使用set
慢,但如果您使用的是 3.6+,则不会很多慢(如果您使用 OrderedDict
的 3.5 或更早版本,它的影响更大;> 3x 运行时间,而3.6+ 加上普通的dict
运行时间只增加了大约 66%)。【参考方案3】:
在使用a
中的值作为键构造的dict
上使用keys
。
b = dict([(i, 1) for i in a]).keys()
或者使用一个集合:
b = [i for i in set(a)]
【讨论】:
【参考方案4】:列表不变的原因是b
开始时是空的。这意味着if item not in b
始终是True
。只有在列表生成后,这个新的非空列表才会分配给变量b
。
【讨论】:
如果我理解正确,这意味着列表理解会一次性添加项目,而不是像循环一样一次检查和添加每个项目。 @Alinwndrld:我认为这不是一个有效的结论。这仅意味着在分配之前评估列表理解。该列表很可能在内部循环构建。【参考方案5】:使用groupby:
>>> from itertools import groupby
>>> a = [1, 2, 3, 3, 5, 9, 6, 2, 8, 5, 2, 3, 5, 7, 3, 5, 8]
>>> [k for k, _ in groupby(sorted(a, key=lambda x: a.index(x)))]
[1, 2, 3, 5, 9, 6, 8, 7]
如果您不关心值首先出现在原始列表中的哪个顺序,请省略 key 参数,例如
>>> [k for k, _ in groupby(sorted(a))]
[1, 2, 3, 5, 6, 7, 8, 9]
您可以使用groupby
做一些很酷的事情。识别多次出现的项目:
>>> [k for k, v in groupby(sorted(a)) if len(list(v)) > 1]
[2, 3, 5, 8]
或者建立一个频率词典:
>>> k: len(list(v)) for k, v in groupby(sorted(a))
1: 1, 2: 3, 3: 4, 5: 4, 6: 1, 7: 1, 8: 2, 9: 1
itertools 模块中有一些非常有用的功能:chain
、tee
和 product
等等!
【讨论】:
【参考方案6】:>>> a = [10,20,30,20,10,50,60,40,80,50,40,0,100,30,60]
>>> [a.pop(a.index(i, a.index(i)+1)) for i in a if a.count(i) > 1]
>>> print(a)
【讨论】:
【参考方案7】:对于 Python 3.6+,与Niek de Klein's mostly excellent solution 相比有一个改进(主要缺陷是它丢失了输入顺序)。由于dict
s 现在是插入顺序的,您可以这样做:
b = list(dict.fromkeys(a))
在早期的 Python 上,你会这样做:
from collections import OrderedDict
b = list(OrderedDict.fromkeys(a))
虽然速度没有那么快(即使 OrderedDict
被移到 C 层,它仍然需要大量开销来支持不支持它们的 dict
避免的重新排序操作)。
【讨论】:
【参考方案8】:>>> from itertools import groupby
>>> repeated_items = [2,2,2,2,3,3,3,3,4,5,1,1,1]
>>> [
... next(group)
... for _, group in groupby(
... repeated_items,
... key=repeated_items.index
... )
... ]
[2, 3, 4, 5, 1]
【讨论】:
聪明的解决方案,我喜欢。缺点是index
调用,使其成为O(n²)
,并且假设输入已经分组(它不适用于[2,1,2]
)。您可以解决这两个问题,并且仍然保留输入顺序,使用修改后的 Schwartzian 变换(需要 from itertools import count, groupby
):[v for v, _ in sorted([next(grp) for _, grp in groupby(sorted(zip(repeated_items, count())), key=lambda x: x[0])], key=lambda x: x[1])]
。可能不值得麻烦,但我喜欢itertools
的疯狂。以上是关于如何使用列表理解从列表中删除重复项? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Python 列表中删除重复项并保持顺序? [复制]
如何通过 Oracle regexp_replace 从空格分隔列表中删除重复项? [复制]
如何通过 Oracle 中的正则表达式从逗号分隔列表中删除重复项,但我不想要重复值? [复制]