如何在保留顺序的同时从列表中删除重复项?

How do I remove duplicates from a list, while preserving order?

提问人:Josh Glover 提问时间:1/26/2009 最后编辑:Trenton McKinneyJosh Glover 更新时间:7/7/2023 访问量:715933

问:

如何在保留顺序的同时从列表中删除重复项?使用集合删除重复项会破坏原始顺序。 有内置的还是 Python 的成语?

python duplicates list-comprehension 唯一

评论


答:

897赞 Markus Jarderot 1/26/2009 #1

在这里,您有一些替代方案: 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.addseen_addseen.addseen.addseen.add

如果您计划在同一数据集上大量使用此函数,也许您最好使用有序集:http://code.activestate.com/recipes/528878/

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

(小的附加说明:总是返回 ,所以 or 以上只是作为尝试集合更新的一种方式,而不是作为逻辑测试的组成部分。seen.add()None

评论

26赞 Markus Jarderot 3/23/2013
@JesseDhillon迭代之间可能会发生变化,而且运行时不够智能,无法排除这种情况。为了安全起见,它每次都必须检查对象。-- 如果你用 看一下字节码,你可以看到它在每次迭代中都为成员执行。ideone.com/tz1Tllseen.adddis.dis(f)LOAD_ATTRadd
6赞 Jens Timmerman 3/11/2014
当我在列表列表上尝试此操作时,我得到: TypeError: unhashable type: 'list'
7赞 user136036 10/25/2014
您的解决方案不是最快的解决方案。在 Python 3(未测试 2)中,这更快(300k 条目列表 - 0.045s(你的)与 0.035s(这个):seen = set(); 返回 [x for x in lines if x not in seen and not seen.add(x)]。我找不到你所做的seen_add线的任何速度效果。
3赞 jamylak 5/20/2015
@user136036 请链接到您的测试。你运行了多少次?是一项改进,但时间可能会受到当时系统资源的影响。有兴趣看到完整的时间安排seen_add
16赞 sleblanc 5/14/2019
对于任何正在编写 Python 代码的人来说,在牺牲可读性和普遍同意的 Python 约定之前,你真的应该三思而后行,只是为了在每个循环中多挤出几纳秒。无论是否进行测试,速度仅可提高 1%。这并不重要。seen_add = seen.add
29赞 Rafał Dowgird 1/26/2009 #2
from itertools import groupby

[key for key, _ in groupby(sortedList)]

列表甚至不必排序,充分条件是将相等的值分组在一起。

编辑:我假设“保持顺序”意味着列表实际上是有序的。如果不是这种情况,那么 MizardX 的解决方案是正确的。

社区编辑:然而,这是“将重复的连续元素压缩为单个元素”的最优雅方法。

评论

1赞 1/26/2009
但这并不能维持秩序!
1赞 Josh Glover 1/26/2009
嗯,这是有问题的,因为我不能保证相等的值被分组在一起,而不会在列表中循环一次,到那时我本可以修剪重复项。
0赞 Rafał Dowgird 1/26/2009
我假设“保持秩序”意味着列表实际上是有序的。
1赞 1/27/2009
也许输入列表的规格有点不清楚。这些值甚至不需要组合在一起:[2, 1, 3, 1]。 那么哪些值要保留,哪些值要删除呢?
1赞 Rafał Dowgird 11/13/2019
@igorkf 忽略对的第二个元素。
0赞 code22 8/6/2011 #3

如果您需要一个衬垫,那么也许这会有所帮助:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

...应该有效,但如果我错了,请纠正我

评论

0赞 code22 12/17/2013
这是一个条件表达式,所以很好
0赞 MERose 4/7/2022
在 Python 3 中,您必须 .from functools import reduce
5赞 zmk 8/22/2011 #4

对于没有可散列类型(例如列表列表),基于 MizardX 的:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]
0赞 Saurabh Hirani 10/9/2011 #5

MizardX的回答很好地收集了多种方法。

这是我在大声思考时想出的:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

评论

0赞 Rivka 9/2/2012
你的解决方案很好,但它需要每个元素的最后外观。第一次出现时,请使用:[x for i,x in enumerate(mylist) if x not in mylist[:i]]
8赞 Nikita Volkov 9/5/2012
由于在列表中搜索是一项操作,并且您对每个项目都执行该操作,因此解决方案的最终复杂性为 。对于这样一个微不足道的问题,这是不可接受的。O(n)O(n^2)
2赞 Zhifeng Hu 11/7/2012 #6

您可以在通过符号“_[1]”构建列表推导式时引用它。
例如,以下函数通过引用元素的列表推导式来唯一化元素列表,而不更改其顺序。

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

演示:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

输出:

[1, 2, 3, 4, 5]

评论

2赞 ely 9/5/2013
另请注意,这将使它成为一个 O(n^2) 操作,其中创建一个集合/字典(具有恒定的查找时间)并仅添加以前未见过的元素将是线性的。
0赞 jamylak 6/7/2015
我相信这是 Python 2.6。是的,它是 O(N^2)
0赞 Glenn Slayden 10/5/2020
@jamylak意味着这仅适用于 Python 2.7 及更早版本,而不是以后
0赞 jamylak 10/6/2020
@GlennSlayden 不,我指的是 Python 2.6。Python 2.6 及更早版本(不确定到底早了多少)。Python 2.6 在当时更受欢迎,所以这就是为什么我只说 Python 2.6 与 Python 2.7 相比
0赞 Glenn Slayden 10/6/2020
@jamylak好的,但我的观点是,不是任何 Python 3.x,我从您 2015 年 6 月 7 日的评论中不清楚。
34赞 dansalmo 4/14/2013 #7
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

独特的→['1', '2', '3', '6', '4', '5']

评论

31赞 loopbackbee 3/20/2014
值得注意的是,这符合n^2
31赞 Martijn Pieters 3/3/2015
哎呀。2 次罢工:使用列表进行成员资格测试(缓慢,每次测试的 O(N) 都很重要),并使用列表理解来理解副作用(在此过程中建立另一个参考列表!None
3赞 jamylak 2/17/2018
我同意@MartijnPieters绝对没有理由对副作用进行列表理解。只需使用循环即可for
26赞 shamrock 5/28/2013 #8

我想如果你想维持秩序,

你可以试试这个:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

或者类似地,你可以这样做:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

您还可以执行此操作:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

它也可以写成这样:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

评论

4赞 Richard 9/4/2013
您的前两个答案假设可以使用排序函数重建列表的顺序,但事实可能并非如此。
6赞 Derek Veit 1/9/2016
大多数答案都集中在性能上。对于不够大而无法担心性能的列表,sorted(set(list1),key=list1.index) 是我见过的最好的东西。没有额外的导入,没有额外的功能,没有额外的变量,而且它相当简单易读。
720赞 12 revs, 7 users 60%jamylak #9

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

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

首先在 PyPy 2.5.0 中引入,并在 CPython 3.6 中作为实现细节采用,在 Python 3.7 中成为语言保证之前,plain 是插入顺序的,甚至比 (也是从 CPython 3.5 开始实现的 C)更有效。因此,到目前为止,最快的解决方案也是最简单的:dictcollections.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 中使用集合s)。list(set(items))dictdict.fromkeyslist(set(items))

重要提示:(见下文)的解决方案在懒惰和支持不可散列输入项方面具有一些独特的优势;如果您需要这些功能,这是唯一可行的解决方案。unique_everseenmore_itertools

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

正如 Raymond 所指出的,在用 C 语言实现的 CPython 3.5 中,丑陋的列表理解黑客比(除非你真的需要最后的列表——即使这样,也只有在输入非常短的情况下)。因此,在性能和可读性方面,CPython 3.5 的最佳解决方案相当于 3.6+ 使用普通:OrderedDictOrderedDict.fromkeysOrderedDictdict

>>> 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库 () 包含一个unique_everseen函数,该函数旨在解决此问题,而不会在列表推导式中出现任何不可读的 () 突变。这也是最快的解决方案:pip install more_itertoolsnot 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

但与配方不同的是,它支持不可散列的项目(以性能为代价;如果其中的所有元素都是不可散列的,则算法变为 ,vs。 如果它们都是可散列的)。itertoolsiterableO(n²)O(n)

重要提示:与这里的所有其他解决方案不同,可以懒惰使用;峰值内存使用量将相同(最终,底层会增长到相同的大小),但如果您不对结果进行调整,只需对其进行迭代,您将能够在找到唯一项目时对其进行处理,而不是等到整个输入被重复数据删除后再处理第一个唯一项目。unique_everseensetlist

Python 3.4 及更早版本,如果性能至关重要第三方模块不可用

您有两种选择:

  1. unique_everseen配方复制并粘贴到您的代码中,并按照上面的示例使用它more_itertools

  2. 使用丑陋的黑客来允许单个 listcomp 检查和更新 a 以跟踪所看到的内容:set

    seen = set()
    [x for x in seq if x not in seen and not seen.add(x)]
    

    以依赖丑陋的黑客为代价:

     not seen.add(x)
    

    它依赖于这样一个事实,即始终返回的就地方法,因此计算结果为 。set.addNonenot NoneTrue

请注意,上面的所有解决方案都是(除了调用不可哈希项的可迭代,即 ,而其他解决方案会立即失败 ),因此当所有解决方案不是最热门的代码路径时,它们都足够高性能。使用哪一个取决于你可以依赖的语言规范/解释器/第三方模块的哪个版本,性能是否至关重要(不要假设它是,通常不是),最重要的是,可读性(因为如果维护此代码的人后来陷入杀戮的情绪,你聪明的微优化可能不值得)。O(n)unique_everseenO(n²)TypeError

评论

5赞 Nakilon 6/14/2013
转换为某种自定义的字典只是为了获取密钥?只是另一个拐杖。
8赞 Imran 6/18/2013
@Nakilon我真的不明白这是一根拐杖。它不公开任何可变状态,所以从这个意义上说它非常干净。在内部,Python 集合是用 dict() (stackoverflow.com/questions/3949310/...) 实现的,所以基本上你只是在做解释器无论如何都会做的事情。
1赞 flornquake 9/10/2013
@EMS 这并不能维持秩序。你也可以做.seen = set(seq)
1赞 user136036 10/25/2014
这个解决方案比上面提到的“黑客”要慢得多。对于我的 300k 条目列表,速度慢了 50 倍以上。
1赞 jamylak 5/20/2015
@CommuSoft 我同意,尽管实际上由于极不可能的最坏情况,它几乎总是 O(n)
2赞 ely 9/11/2013 #10

借用在定义 Haskell 列表函数时使用的递归思想,这将是一种递归方法:nub

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

例如:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

我尝试了它来增加数据大小,并看到了亚线性时间复杂度(不是确定的,但建议这对于正常数据应该没问题)。

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

我还认为有趣的是,这可以很容易地被其他操作推广为唯一性。喜欢这个:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

例如,出于唯一性目的,您可以传入一个函数,该函数使用四舍五入到同一整数的概念,就好像它是“相等”一样,如下所示:

def test_round(x,y):
    return round(x) != round(y)

然后 unique(some_list, test_round) 将提供列表的唯一元素,其中唯一性不再意味着传统的相等性(通过使用任何类型的基于集合或基于字典键的方法来解决这个问题,这意味着只取第一个元素,对于元素可能舍入到的每个可能的整数 K,该元素四舍五入为 K, 例如:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

评论

1赞 ely 9/11/2013
请注意,当唯一元素的数量相对于元素总数非常大时,性能会变差,因为每个连续递归调用的使用几乎不会从前一个调用中受益。但是,如果唯一元素的数量相对于数组大小较小,则这应该表现良好。filter
0赞 dominecf 10/2/2013 #11

使用数组的相对有效的方法:_sorted_numpy

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

输出:

array([ 1,  3,  8, 12])
11赞 abarnert 10/10/2013 #12

对于另一个非常古老的问题的另一个很晚的答案:

itertools 配方有一个函数,它使用 set 技术来执行此操作,但是:seen

  • 处理标准函数。key
  • 不使用不合时宜的黑客。
  • 通过预绑定来优化循环,而不是查找 N 次。( 也这样做,但有些版本不这样做。seen.addf7
  • 通过使用 来优化循环,因此您只需遍历 Python 中的唯一元素,而不是所有元素。(当然,你仍然在里面遍历所有这些,但那是在 C 语言中,而且速度要快得多。ifilterfalseifilterfalse

它实际上比?这取决于您的数据,因此您必须对其进行测试并查看。如果你最终想要一个列表,请使用 listcomp,这里没有办法做到这一点。(您可以直接而不是 ing,或者您可以将生成器馈送到函数中,但两者都不能像 listcomp 中的LIST_APPEND一样快。无论如何,通常情况下,挤出几微秒并不像拥有一个易于理解、可重用、已经编写的函数那么重要,当你想要装饰时,它不需要 DSU。f7f7appendyieldlist

与所有食谱一样,它也有更多iterools

如果你只想要无情况,你可以将其简化为:key

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

评论

0赞 jamylak 6/7/2015
我完全忽略了这显然是最好的答案。一个简单的方法比我的方法快得多,比公认的答案好得多,我认为库下载是值得的。我将进入社区维基我的答案并将其添加进去。more-itertoolsfrom more_itertools import unique_everseenlist(unique_everseen(items))
0赞 user1969453 4/25/2014 #13

你可以做一个丑陋的列表理解黑客。

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

评论

0赞 Evpok 2/28/2015
更喜欢 .i,e in enumerate(l)l[i] for i in range(len(l))
0赞 kylieCatt 11/7/2014 #14
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

一个生成器表达式,它使用集合的 O(1) 查找来确定是否在新列表中包含元素。

评论

1赞 John Coleman 1/4/2017
巧妙地使用生成器表达式,该表达式取决于被扩展的事物(因此 +1),但在每个阶段(线性)重新计算,这使整体方法变得二次。事实上,这几乎可以肯定比简单地使用 .为单个成员资格测试制作一个集合不值得花费创建集合的费用。不过,这是一个有趣的方法。extendset(n)ele in n
4赞 Sergey Bershadsky 4/27/2015 #15

减少变体速度提高 5 倍,但更复杂

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

解释:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]
0赞 Ilya Prokin 5/17/2015 #16

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

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]]
-1赞 Lei 7/18/2015 #17

如果您经常使用 pandas,并且美观性优于性能,那么请考虑内置功能:pandas.Series.drop_duplicates

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

定时:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop
0赞 Soham Joshi 1/13/2016 #18

这将保持秩序并在 O(n) 时间内运行。基本上,这个想法是在发现重复项的地方创建一个洞,然后将其沉入底部。使用读取和写入指针。每当找到重复项时,只有读取指针前进,而写入指针停留在重复条目上以覆盖它。

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]
-1赞 Rob Murray 1/27/2016 #19

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

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']

评论

0赞 Jean-François Fabre 11/21/2018
这是 O(N**2) 复杂度 + 每次列表切片。
161赞 Raymond Hettinger 10/3/2016 #20

在 CPython 3.6+(以及从 Python 3.7+ 开始的所有其他 Python 实现)中,字典是有序的,因此从可迭代对象中删除重复项同时保持其原始顺序的方法是:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

在 Python 3.5 及更低版本(包括 Python 2.7)中,使用 OrderedDict。我的时间安排表明,这现在是 Python 3.5 各种方法中最快和最短的(当它获得 C 实现时;在 3.5 之前,它仍然是最清晰的解决方案,尽管不是最快的)。

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

评论

9赞 Mr_and_Mrs_D 5/31/2018
唯一的问题是可迭代的“元素”必须是可散列的 - 最好为具有任意元素的可迭代对象(作为列表列表)提供等效项
0赞 Arthur 1/10/2019
字典上的插入顺序迭代提供了比删除重复项服务更多用例的功能。例如,科学分析依赖于非确定性字典迭代不支持的可重复计算。可重复性是计算科学建模中当前的主要目标,因此我们欢迎这一新功能。虽然我知道使用确定性字典进行构建是微不足道的,但高性能的确定性将帮助更天真的用户开发可重现的代码。set()
1赞 Raymond Hettinger 6/25/2020
@colidyre 是的,那行得通。速度差异小可能是由于操作员不必查找内置功能。还有一个明确的问题需要考虑。
2赞 ShadowRanger 12/28/2021
@RaymondHettinger:查找成本很小(3.8 的查找成本更小);主要优点是避免了构造函数代码路径(需要构造 for 并传递指针 as ,然后分别调用大部分空参数和分别参数,后者必须通过通用参数解析代码,所有这些都要传递 0-1 位置参数)。不过,从 3.9 开始,通过 vectorcall 协议绕过了大部分,将我机器上的增量收益从 60-70 ns (3.8.5) 减少到 20-30 ns (3.10.0)。LOAD_GLOBALtupleargsNULLkwargsdict__new____init__list()
1赞 Paul Draper 1/17/2023
@Mr_and_Mrs_D,如果不进行哈希处理,就不可能对 O(n) 中的元素进行重复数据删除。对任意元素进行重复数据删除将是 O(n^2)。
16赞 MSeifert 1/11/2017 #21

只是为了从外部模块1 添加此类功能的另一个(非常高性能的)实现:iteration_utilities.unique_everseen

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

计时

我做了一些计时(Python 3.6),这些表明它比我测试的所有其他替代方案都快,包括 和 :OrderedDict.fromkeysf7more_itertools.unique_everseen

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

enter image description here

只是为了确保我还做了一个带有更多重复项的测试,只是为了检查它是否有所作为:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

enter image description here

一个只包含一个值:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

enter image description here

在所有这些情况下,该功能都是最快的(在我的计算机上)。iteration_utilities.unique_everseen


此函数还可以处理输入中不可哈希的值(但是,当值可哈希时,使用性能而不是性能)。iteration_utilities.unique_everseenO(n*n)O(n)

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 免责声明:我是该软件包的作者。

评论

0赞 Alex 7/5/2019
我不明白这条线的必要性:--这是基准测试所必需的吗?seen_add = seen.add
0赞 MSeifert 7/6/2019
@Alex 这是本回答中给出的方法。在那里问会更有意义。我只是使用该答案中的方法来比较时间。
0赞 5/15/2020
你能把方法添加到你的图表中吗?dict.fromkeys()
0赞 MSeifert 5/15/2020
我真的不确定我是否能尽快完成同样的计时。你认为它比 ?ordereddict.fromkeys
0赞 Roko Mijic 6/4/2020
“这个 iteration_utilities.unique_everseen 函数还可以处理输入中不可散列的值”——是的,这真的很重要。如果你有一个字典的字典列表,这是完成这项工作的唯一方法,即使是在小规模的情况下。
44赞 Alexander 8/18/2017 #22

不要踢死马(这个问题很老了,已经有很多很好的答案了),但这里有一个使用熊猫的解决方案,它在许多情况下都非常快,而且使用起来非常简单。

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

评论

1赞 baxx 6/20/2021
有用,但不保留排序。 确实如此。more_itertools.unique_everseen
44赞 timgeb 3/2/2018 #23

Python 3.7 及更高版本中,字典可以保证记住其键插入顺序。这个问题的答案总结了当前的事态。

因此,该解决方案变得过时,并且没有任何导入语句,我们可以简单地发出:OrderedDict

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]
1赞 this.srivastava 5/28/2019 #24

消除序列中的重复值,但保留其余项的顺序。使用通用发电机功能。

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
4赞 Ahmed4end 4/4/2020 #25

这是一种简单的方法:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=list1.index)

这给出了输出:

["hello", " ", "w", "o", "r", "l", "d"]
5赞 timgeb 5/26/2020 #26

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 方法将其转换为列表。

评论

1赞 seralouk 6/3/2020
好东西。我从来没有想过用熊猫来做这件事,但它确实有效
0赞 Joe Ferndz 12/5/2020
list(pd.unique(a))会将其转换为 OP 想要的正常列表。对 Pandas 解决方案投了赞成票。从没想过要这样做。
2赞 smci 12/10/2020
pd.unique(lst).tolist()是更好的成语。抄送:@JoeFerndz
0赞 Jože Ws 6/1/2020 #27

一行列表理解:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]
0赞 Franco 8/23/2020 #28
x = [1, 2, 1, 3, 1, 4]

# brute force method
arr = []
for i in x:
  if not i in arr:
    arr.insert(x[i],i)

# recursive method
tmp = []
def remove_duplicates(j=0):
    if j < len(x):
      if not x[j] in tmp:
        tmp.append(x[j])
      i = j+1  
      remove_duplicates(i)

      

remove_duplicates()
1赞 Glenn Slayden 7/9/2021 #29

1.这些解决方案很好...
为了在保持秩序的同时删除重复项,本页其他地方提出的优秀解决方案:

seen = set()
[x for x in seq if not (x in seen or seen.add(x))]

和变体,例如:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

确实很受欢迎,因为它们简单、简约,并且部署了正确的哈希以获得最佳效率。关于这些的主要抱怨似乎是,在逻辑表达式中使用不变的“返回”方法作为常量(因此是多余的/不必要的)值——仅仅是为了它的副作用——是笨拙和/或令人困惑的。Noneseen.add(x)

阿拉伯数字。。。。但是他们每次迭代都会浪费一次哈希查找。
令人惊讶的是,考虑到关于这个话题的讨论和辩论的数量,实际上对代码有一个重大的改进,似乎被忽视了。如图所示,每个“测试和设置”迭代都需要两次哈希查找:第一次用于测试成员资格,然后再次实际添加值。由于第一个操作保证了第二个操作将始终成功,因此这里存在浪费的重复工作。而且,由于这里的整体技术非常高效,因此多余的哈希查找最终可能会成为剩余工作中最昂贵的部分。
x not in seenseen.add(x)

3. 相反,让布景完成它的工作!
请注意,上面的示例只是在预先知道这样做总是会导致集合成员资格增加的情况下调用。它本身永远没有机会拒绝重复;我们的代码片段基本上已经篡夺了这个角色。使用显式的两步测试和设置代码正在剥夺其排除这些重复项的核心能力。
set.addsetset

4. 单哈希查找代码:
以下版本将每次迭代的哈希查找次数减少了一半,从两个减少到一个。

seen = set()
[x for x in seq if len(seen) < len(seen.add(x) or seen)]

评论

1赞 Kelly Bundy 8/13/2022
这确实需要一个基准。函数调用相对昂贵,因此如果速度较慢,我一点也不会感到惊讶。
1赞 Kelly Bundy 8/13/2022
我现在用各种序列做了一个,这确实要得多。请出示您的证据来证明您的主张“这提高了性能 [...]相当”。
0赞 Glenn Slayden 8/23/2022
@KellyBundy 感谢您的反馈;我编辑了我的答案,删除了未经证实的说法。
1赞 Nico Schlömer 2/24/2023 #30

我将所有相关答案与 perfplot 进行了比较,发现,

list(dict.fromkeys(data))

是最快的。这也适用于小型 numpy 数组。对于较大的 numpy 数组,实际上是最快的。pandas.unique

enter image description here


重现绘图的代码:

from collections import OrderedDict
from functools import reduce
from itertools import groupby

import numpy as np
import pandas as pd
import perfplot
from more_itertools import unique_everseen as ue


def dict_fromkeys(data):
    return list(dict.fromkeys(data))


def unique_everseen(data):
    return list(ue(data))


def seen_add(data):
    seen = set()
    seen_add = seen.add
    return [x for x in data if not (x in seen or seen_add(x))]


def ordereddict_fromkeys(data):
    return list(OrderedDict.fromkeys(data))


def pandas_drop_duplicates(data):
    return pd.Series(data).drop_duplicates().tolist()


def pandas_unique(data):
    return pd.unique(data)


def itertools_groupby(data):
    return [key for key, _ in groupby(data)]


def reduce_tricks(data):
    return reduce(
        lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r,
        data,
        ([], set()),
    )[0]


b = perfplot.bench(
    setup=lambda n: np.random.randint(100, size=n).tolist(),
    kernels=[
        dict_fromkeys,
        unique_everseen,
        seen_add,
        ordereddict_fromkeys,
        pandas_drop_duplicates,
        pandas_unique,
        reduce_tricks,
    ],
    n_range=[2**k for k in range(20)],
)
b.save("out.png")
b.show()

评论

1赞 Kelly Bundy 2/24/2023
实际上。。。不仅要改变输入大小,还要改变输出大小。即重复量。MSeifert 的回答做得相当不错,它的三个图从“没有重复”到“只有重复”。