如何从列表中删除重复项,同时保持顺序?使用集合删除重复项会破坏原始顺序。 是否有内置的或python的习语?


当前回答

这里有一些替代选项: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))]

为什么要赋值。添加到seen_add而不是只调用see . Add ?Python是一种动态语言,解析可见。每次迭代添加比解析一个局部变量代价更大。观察。Add可能会在迭代之间发生更改,而运行时还不够聪明,无法排除这种情况。为了安全起见,它必须每次检查对象。

如果您计划在同一个数据集上大量使用这个函数,那么使用一个有序集可能会更好:http://code.activestate.com/recipes/528878/

O(1)每次操作的插入、删除和成员检查。

(小额外注意:see .add()总是返回None,所以以上值只是作为一种尝试更新集合的方式,而不是逻辑测试的组成部分。)

其他回答

Pandas用户应该查看Pandas .unique。

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

该函数返回一个NumPy数组。如果需要,可以使用tolist方法将其转换为列表。

这里有一些替代选项: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))]

为什么要赋值。添加到seen_add而不是只调用see . Add ?Python是一种动态语言,解析可见。每次迭代添加比解析一个局部变量代价更大。观察。Add可能会在迭代之间发生更改,而运行时还不够聪明,无法排除这种情况。为了安全起见,它必须每次检查对象。

如果您计划在同一个数据集上大量使用这个函数,那么使用一个有序集可能会更好:http://code.activestate.com/recipes/528878/

O(1)每次操作的插入、删除和成员检查。

(小额外注意:see .add()总是返回None,所以以上值只是作为一种尝试更新集合的方式,而不是逻辑测试的组成部分。)

最佳解决方案因Python版本和环境限制而异:

Python 3.7+(大多数解释器支持3.6,作为实现细节):

在PyPy 2.5.0中首次引入,并在CPython 3.6中作为实现细节采用,在Python 3.7中成为语言保证之前,plain dict是插入顺序的,甚至比collections.OrderedDict(也是在CPython 3.5中实现的C)更有效。到目前为止,最快的解决方案也是最简单的:

>>> items = [1, 2, 0, 1, 3, 2]
>>> list(dict.fromkeys(items))  # Or [*dict.fromkeys(items)] if you prefer
[1, 2, 0, 3]

像list(set(items))一样,这将所有工作推到C层(在CPython上),但由于dicts是插入顺序的,dict.fromkeys不会失去顺序。它比list(set(items))慢(通常需要50-100%的时间),但比任何其他保持顺序的解决方案快得多(在listcomp中使用set需要大约一半的时间)。

重要提示:more_itertools的unique_everseen解决方案(见下面)在惰性和支持非哈希输入项方面有一些独特的优势;如果您需要这些特性,那么这是唯一可行的解决方案。

Python 3.5(以及性能不重要的所有旧版本)

正如Raymond指出的,在CPython 3.5中,OrderedDict是用C实现的,丑陋的列表理解比OrderedDict.fromkeys要慢(除非你真的需要在结尾使用列表——即使这样,也只有在输入非常短的情况下)。因此,在性能和可读性上,CPython 3.5的最佳解决方案是OrderedDict等价于3.6+使用普通dict:

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

在CPython 3.4及更早版本上,这将比其他一些解决方案慢,所以如果分析显示您需要更好的解决方案,请继续阅读。

Python 3.4或更早版本,如果性能很关键且第三方模块是可接受的

正如@abarnert指出的那样,more_itertools库(pip install more_itertools)包含一个unique_everseen函数,该函数是为了解决这个问题而构建的,而不会在列表推导中出现任何不可读(not seen.add)的变化。这也是最快的解决方案:

>>> from more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

只是一个简单的库导入,没有黑客。

该模块正在调整itertools配方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

但与itertools食谱不同的是,它支持不可哈希项(以性能为代价;如果iterable中的所有元素都是不可哈希的,则算法变成O(n²)vs. O(n)(如果它们都是可哈希的)。

重要提示:与这里所有其他解决方案不同,unique_everseen可以惰性使用;峰值内存使用将是相同的(最终,底层集合增长到相同的大小),但如果不列出结果,只需迭代它,就能够在找到唯一项时处理它们,而不是等到整个输入重复数据删除后才处理第一个唯一项。

Python 3.4及更早版本,如果性能非常关键且第三方模块不可用

你有两个选择:

复制并粘贴unique_everseen配方到您的代码中,并在上面的more_itertools示例中使用它 使用丑陋的hack,允许一个listcomp检查和更新一个集,以跟踪所看到的内容: Seen = set() [x for x in seq if x not in seen and not seen.add(x)] 以依赖丑陋的黑客为代价: 不是seen.add (x) 这取决于一个事实。add是一个原地方法,总是返回None,所以not None的值为True。

Note that all of the solutions above are O(n) (save calling unique_everseen on an iterable of non-hashable items, which is O(n²), while the others would fail immediately with a TypeError), so all solutions are performant enough when they're not the hottest code path. Which one to use depends on which versions of the language spec/interpreter/third-party modules you can rely on, whether or not performance is critical (don't assume it is; it usually isn't), and most importantly, readability (because if the person who maintains this code later ends up in a murderous mood, your clever micro-optimization probably wasn't worth it).

一个简单的递归解决方案:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

不使用导入模块或集的解决方案:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

给输出:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']