如何从列表中删除重复项,同时保持顺序?
如何从列表中删除重复项,同时保持顺序?使用集合删除重复项会破坏原始订单。是否有内置或Pythonic成语?
如何从列表中删除重复项,同时保持顺序?使用集合删除重复项会破坏原始订单。是否有内置或Pythonic成语?
在这里,您有一些选择:http://www.peterbe.com/plog/uniqifiers-benchmark
最快的一个:
def f7(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
为什么要分配 到 而不是仅仅呼叫 ?Python是一种动态语言,解析每次迭代比解析局部变量的成本更高。 可能在迭代之间发生了变化,并且运行时不够智能,无法排除这种情况。为了安全起见,它必须每次检查对象。seen.add
seen_add
seen.add
seen.add
seen.add
如果您计划在同一数据集上大量使用此函数,也许最好使用有序集:http://code.activestate.com/recipes/528878/
O(1) 每个操作的插入、删除和成员检查。
(小的附加说明:总是返回,所以或
以上只是作为尝试集合更新的一种方式,而不是作为逻辑测试的组成部分。seen.add()
None
最佳解决方案因 Python 版本和环境约束而异:
首先在PyPy 2.5.0中引入,并在CPython 3.6中作为实现细节采用,在Python 3.7中成为语言保证之前,plain是插入顺序的,甚至比(也是CPython 3.5实现的C)更有效。因此,到目前为止,最快的解决方案也是最简单的:dict
collections.OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(dict.fromkeys(items)) # Or [*dict.fromkeys(items)] if you prefer
[1, 2, 0, 3]
像这样将所有工作推到C层(在CPython上),但由于s是按插入顺序排列的,因此不会丢失排序。它比(通常需要50-100%的时间长),但比任何其他保持顺序的解决方案都要快得多(大约需要一半的黑客攻击时间,涉及在listcomp中使用sets
)。list(set(items))
dict
dict.fromkeys
list(set(items))
重要提示:来自(见下文)的解决方案在懒惰和支持不可哈希输入项方面具有一些独特的优势;如果您需要这些功能,这是唯一可行的解决方案。unique_everseen
more_itertools
正如Raymond所指出的,在CPython 3.5中,在C中实现的丑陋列表理解黑客比(除非你实际上需要最后的列表 - 即使这样,也只有在输入非常短的情况下)因此,在性能和可读性方面,CPython 3.5的最佳解决方案相当于3.6 +使用普通:OrderedDict
OrderedDict.fromkeys
OrderedDict
dict
>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]
在CPython 3.4及更早版本上,这将比其他一些解决方案慢,因此,如果分析显示您需要更好的解决方案,请继续阅读。
正如@abarnert所指出的,more_itertools
库 () 包含一个 unique_everseen
函数,该函数旨在解决此问题,而不会在列表推导中出现任何不可读 () 突变。这也是最快的解决方案:pip install more_itertools
not seen.add
>>> from more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]
只需一个简单的库导入,没有黑客攻击。
该模块正在调整迭代工具配方unique_everseen
如下所示:
def unique_everseen(iterable, key=None):
"List unique elements, preserving order. Remember all elements ever seen."
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
# unique_everseen('ABBCcAD', str.lower) --> A B C D
seen = set()
seen_add = seen.add
if key is None:
for element in filterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
else:
for element in iterable:
k = key(element)
if k not in seen:
seen_add(k)
yield element
但与配方不同的是,它支持不可哈希的项目(以性能成本;如果中的所有元素都是不可哈希的,则算法变为 , vs。 如果它们都是可哈希的)。itertools
iterable
O(n²)
O(n)
重要提示:与此处的所有其他解决方案不同,可以懒惰地使用;峰值内存使用量将相同(最终,底层内存增长到相同的大小),但是如果您不对结果进行迭代,则只需对其进行迭代,您就可以在找到唯一项目时对其进行处理,而不是等到整个输入经过重复数据删除后再处理第一个唯一项目。unique_everseen
set
list
您有两种选择:
将unique_everseen
配方复制并粘贴到您的代码中,并根据上面的示例使用它more_itertools
使用丑陋的黑客来允许单个listcomp检查和更新,以跟踪所看到的内容:set
seen = set()
[x for x in seq if x not in seen and not seen.add(x)]
以依赖丑陋的黑客为代价:
not seen.add(x)
它依赖于这样一个事实,即始终返回,因此计算结果为 。set.add
None
not None
True
请注意,上述所有解决方案都是(除了调用不可哈希项目的可迭代,即 ,而其他解决方案会立即失败,并带有 a),因此当它们不是最热门的代码路径时,所有解决方案的性能都足够高。使用哪一个取决于你可以依赖哪个版本的语言规范/解释器/第三方模块,性能是否至关重要(不要假设它是关键的;它通常不是),最重要的是,可读性(因为如果维护此代码的人后来陷入杀戮的情绪,你聪明的微优化可能不值得)。O(n)
unique_everseen
O(n²)
TypeError