【问题标题】:How do you remove duplicates from a list whilst preserving order?如何在保留顺序的同时从列表中删除重复项?
【发布时间】:2010-10-03 13:44:59
【问题描述】:

是否有一个内置函数可以从 Python 中的列表中删除重复项,同时保留顺序?我知道我可以使用集合来删除重复项,但这会破坏原始顺序。我也知道我可以像这样滚动自己:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

(感谢unwindcode sample。)

但如果可能的话,我想利用内置的或更 Pythonic 的习语。

相关问题:In Python, what is the fastest algorithm for removing duplicates from a list so that all elements are unique while preserving order?

【问题讨论】:

  • 您可能需要考虑对这个答案stackoverflow.com/a/17016257/1219006 进行 2020 年的编辑,这似乎是 Python 3.6(cpython)-7(all pythons)+ list(dict.fromkeys(items))现在的最佳解决方案

标签: python list duplicates unique


【解决方案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))]

为什么将seen.add 分配给seen_add 而不仅仅是调用seen.add? Python 是一种动态语言,每次迭代解析seen.add 比解析局部变量的成本更高。 seen.add 可能在迭代之间发生了变化,并且运行时不够聪明,无法排除这种情况。为了安全起见,它必须每次都检查对象。

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

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

(补充一点:seen.add() 总是返回 None,所以上面的 or 只是作为尝试更新集合的一种方式,而不是作为逻辑测试。)

【讨论】:

  • @JesseDhillon seen.add 可能在迭代之间发生了变化,并且运行时不够聪明,无法排除这种情况。为了安全起见,它必须每次都检查对象。 -- 如果您查看带有dis.dis(f) 的字节码,您可以看到它在每次迭代中为add 成员执行LOAD_ATTRideone.com/tz1Tll
  • 当我在列表列表上尝试此操作时,我得到:TypeError: unhashable type: 'list'
  • 您的解决方案不是最快的。在 Python 3(未测试 2)中,这更快(300k 条目列表 - 0.045s(你的)vs 0.035s(这个):seen = set(); return [x for x in lines if x not in seen and not seen.add(x)]。我找不到你所做的 seen_add 行的任何速度效果。
  • @user136036 请链接到您的测试。您运行了多少次?seen_add 是一种改进,但时间可能会受到当时系统资源的影响。有兴趣查看完整的时间
  • 对于任何编写 Python 代码的人来说,在牺牲可读性和普遍同意的 Python 约定之前,您真的应该三思而后行,只是为了在每个循环中多挤出几纳秒。使用和不使用seen_add = seen.add 进行测试只会使速度提高 1%。这几乎不重要。
【解决方案2】:
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

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

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

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

【讨论】:

  • 但这并不能保持秩序!
  • Hrm,这是有问题的,因为我不能保证相等的值被组合在一起而不在列表上循环一次,到那时我可以修剪重复的。
  • 我认为“保留顺序”意味着列表实际上是有序的。
  • 可能输入列表的规范有点不清楚。这些值甚至不需要组合在一起:[2, 1, 3, 1]。那么要保留哪些值,要删除哪些值呢?
  • @igorkf 忽略对的第二个元素。
【解决方案3】:

如果您需要一个班轮,那么这可能会有所帮助:

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

...应该可以,但如果我错了,请纠正我

【讨论】:

【解决方案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 ) )]

【讨论】:

    【解决方案5】:

    MizardX 的回答提供了多种方法的良好集合。

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

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

    【讨论】:

    • 您的解决方案很好,但它需要每个元素的最后一次出现。要第一次出现,请使用:[x for i,x in enumerate(mylist) if x not in mylist[:i]]
    • 由于在列表中搜索是一个O(n) 操作并且您对每个项目执行它,因此您的解决方案的最终复杂性将是O(n^2)。对于这样一个微不足道的问题,这是不可接受的。
    【解决方案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]
    

    【讨论】:

    • 另请注意,这将使其成为 O(n^2) 操作,其中创建 set/dict(具有恒定查找时间)并仅添加以前未见过的元素将是线性的。
    • 我相信这只是 Python 2.6。是的,它是 O(N^2)
    • @jamylak 的意思是,这仅适用于 Python 2.7 及更早版本,不适用于更高版本
    • @GlennSlayden 不,我的意思只是 Python 2.6。 Python 2.6 及更早版本(不确定究竟早了多少)。 Python 2.6 在当时更流行,所以我说 Python 2.6 只是与 Python 2.7 相比
    • @jamylak 好的,但我的意思是,不是任何 Python 3.x,我在 2015 年 6 月 7 日的评论中并不清楚。
    【解决方案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']

    【讨论】:

    • 值得注意的是这个运行在n^2
    • 伊克。 2 次打击:使用成员资格测试列表(缓慢,O(N) 对于每个测试)并使用列表理解来处理副作用(在此过程中构建另一个Nonereferences 列表!)
    • 我同意@MartijnPieters 的观​​点,绝对没有理由让列表理解产生副作用。只需使用 for 循环代替
    【解决方案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 
    

    【讨论】:

    • 您的前两个答案假设可以使用排序功能重建列表的顺序,但事实可能并非如此。
    • 大多数答案都集中在性能上。对于不够大而不必担心性能的列表, sorted(set(list1),key=list1.index) 是我见过的最好的东西。没有额外的导入,没有额外的函数,没有额外的变量,而且相当简单易读。
    【解决方案9】:

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

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

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

    >>> 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% 的时间),但比任何其他订单保留解决方案都快(大约需要 hacks involving use of sets in a listcomp 的一半时间)。

    重要提示more_itertoolsunique_everseen 解决方案(见下文)在惰性和支持不可散列的输入项方面具有一些独特的优势;如果您需要这些功能,只有的解决方案可以工作。

    Python 3.5(以及所有旧版本,如果性能不是关键

    作为 Raymond pointed out,在 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²),而O(n) 如果它们'都是可散列的)。

    重要提示:与这里的所有其他解决方案不同,unique_everseen 可以懒惰地使用;峰值内存使用量将相同(最终,底层set 增长到相同大小),但如果您不listify 结果,您只需对其进行迭代,您将能够处理独特的项目当它们被发现时,而不是等到整个输入都被删除后才处理第一个唯一项。

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

    你有两个选择:

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

    2. 使用丑陋的 hack 让单个 listcomp 同时检查和更新 set 以跟踪所看到的内容:

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

      以依赖 丑陋的 hack 为代价:

       not seen.add(x)
      

      这依赖于set.add 是一个始终返回None 的就地方法这一事实,因此not None 的计算结果为True

    请注意,所有上述解决方案都是O(n)(保存在不可散列项的可迭代项上调用unique_everseen,即O(n²),而其他解决方案将立即失败并返回TypeError),因此所有解决方案在不是最热门的代码路径时都具有足够的性能。使用哪一个取决于您可以依赖的语言规范/解释器/第三方模块的版本,性能是否至关重要(不要假设它是关键;通常不是),最重要的是可读性(因为如果维护这段代码的人后来陷入了杀气,你聪明的微优化可能就不值得了)。

    【讨论】:

    • 转换为某种自定义类型的 dict 只是为了获取密钥?只是另一个拐杖。
    • @Nakilon 我真的不明白它是个拐杖。它不会暴露任何可变状态,因此在这个意义上它非常干净。在内部,Python 集合是用 dict() (stackoverflow.com/questions/3949310/…) 实现的,所以基本上你只是在做解释器会做的事情。
    • @EMS 这不会保留订单。你也可以这样做seen = set(seq)
    • 这个解决方案比提到的“hack”慢得多。对于我的 300k 条目列表,速度要慢 50 倍。
    • @CommuSoft 我同意,尽管实际上它几乎总是 O(n),因为极不可能出现最坏的情况
    【解决方案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)
    

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

    【讨论】:

    • 请注意,当唯一元素的数量相对于元素总数非常大时,性能会变差,因为每次连续递归调用对 filter 的使用将几乎不会从前一次调用中受益.但是如果唯一元素的数量相对于数组大小来说很小,这应该会表现得很好。
    【解决方案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])
    

    【讨论】:

      【解决方案12】:

      另一个很老的问题的另一个很晚的答案:

      itertools recipes 具有执行此操作的函数,使用 seen 设置技术,但是:

      • 处理标准的key 函数。
      • 不使用不合时宜的技巧。
      • 通过预绑定seen.add而不是查找N次来优化循环。 (f7 也这样做,但有些版本没有。)
      • 使用ifilterfalse 优化循环,因此您只需循环遍历Python 中的唯一元素,而不是所有元素。 (当然,您仍然会在 ifilterfalse 中遍历所有这些,但这是在 C 中,而且速度要快得多。)

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

      与所有食谱一样,它也可以在more-iterools 中找到。

      如果您只想要 no-key 的情况,您可以将其简化为:

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

      【讨论】:

      • 我完全忽略了more-itertools 这显然是最好的答案。一个简单的from more_itertools import unique_everseenlist(unique_everseen(items)) 比我的方法快得多,也比公认的答案好得多,我认为图书馆下载是值得的。我要去社区 wiki 我的答案并添加它。
      【解决方案13】:

      你可以做一些丑陋的列表理解技巧。

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

      【讨论】:

      • 首选i,e in enumerate(l) 而不是l[i] for i in range(len(l))
      【解决方案14】:
      l = [1,2,2,3,3,...]
      n = []
      n.extend(ele for ele in l if ele not in set(n))
      

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

      【讨论】:

      • 巧妙地使用 extend 和生成器表达式,该表达式取决于被扩展的事物(所以 +1),但 set(n) 在每个阶段都被重新计算(这是线性的),这会影响整体逼近二次方。事实上,这几乎肯定比简单地使用ele in n 更糟糕。为单个成员资格测试制作一个集合不值得为创建集合而付出代价。不过——这是一种有趣的方法。
      【解决方案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]
      

      【讨论】:

        【解决方案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]]
        

        【讨论】:

          【解决方案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
          

          【讨论】:

            【解决方案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]
            

            【讨论】:

              【解决方案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']
              

              【讨论】:

              • 这是 O(N**2) 复杂度 + 每次列表切片。
              【解决方案20】:

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

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

              【讨论】:

              • 唯一的问题是可迭代的“元素”必须是可散列的 - 如果有任意元素的可迭代对象(作为列表的列表),那就太好了
              • 在 dict 上的插入顺序迭代提供了服务于更多用例而不是删除重复项的功能。例如,科学分析依赖于非确定性 dict 迭代不支持的可重现计算。可重复性是当前计算科学建模的主要目标,因此我们欢迎这一新功能。虽然我知道用确定性 dict 构建是微不足道的,但高性能、确定性的 set() 将帮助更多天真的用户开发可重现的代码。
              • 使用[*dict.fromkeys('abracadabra')](解包)而不是调用函数list(...)怎么样?在我的测试中,这更快,尽管只能检测到非常小的差异。所以我不确定这是否只是巧合。
              • @colidyre 是的,这会起作用。小的速度差异可能是由于操作员不必查找内置函数。还需要考虑一个明确的问题。
              • @RaymondHettinger:查找成本很小(使用 3.8 的 LOAD_GLOBAL 变得更小);主要优点是避免构造函数代码路径(需要为args 构造tuple 并将NULL 指针作为kwargs dict 传递,然后分别调用大部分为空的__new____init__,后者则必须通过广义参数解析代码,全部传递 0-1 位置参数)。不过,从 3.9 开始,list() 通过 vectorcall 协议绕过了大部分,将我机器上的增量收益从 60-70 ns (3.8.5) 减少到 20-30 ns (3.10.0)。
              【解决方案21】:

              只是为了从外部模块添加另一个(非常高效的)此类功能的实现1iteration_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()
              

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

              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()
              

              而一个只包含一个值:

              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()
              

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


              这个iteration_utilities.unique_everseen 函数还可以处理输入中不可散列的值(但是当这些值是可散列的时,它的性能是O(n*n) 而不是O(n) 的性能)。

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

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

              【讨论】:

              • 我不明白这一行的必要性:seen_add = seen.add -- 基准测试需要这个吗?
              • @Alex 这是this answer 中给出的方法。在那里问它会更有意义。我只是使用该答案中的方法来比较时间。
              • 您可以将dict.fromkeys() 方法添加到您的图表吗?
              • 我不太确定我是否也有同样的事情要尽快安排时间。你觉得比ordereddict.fromkeys快很多吗?
              • “这个 iteration_utilities.unique_everseen 函数还可以处理输入中不可散列的值”——是的,这非常重要。如果您有 dicts of dicts 等的 dicts 列表,这是完成这项工作的唯一方法,即使是小规模。
              【解决方案22】:

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

              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]
              

              【讨论】:

              • 有用,但不保留顺序。 more_itertools.unique_everseen 确实如此。
              【解决方案23】:

              Python 3.7 及更高版本中,字典是 guaranteed 以记住它们的键插入顺序。 this问题的答案总结了当前的状况。

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

              >>> lst = [1, 2, 1, 3, 3, 2, 4]
              >>> list(dict.fromkeys(lst))
              [1, 2, 3, 4]
              

              【讨论】:

                【解决方案24】:

                就地方法

                这种方法是二次的,因为我们对列表中的每个元素都进行了线性查找(为此,我们必须加上重新排列列表的成本,因为del s)。

                也就是说,如果我们从列表的末尾开始向原点前进,删除其左侧子列表中存在的每个术语,则可以就地操作

                代码中的这个想法很简单

                for i in range(len(l)-1,0,-1): 
                    if l[i] in l[:i]: del l[i] 
                

                实现的简单测试

                In [91]: from random import randint, seed                                                                                            
                In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
                In [93]: for i in range(len(l)-1,0,-1): 
                    ...:     print(l) 
                    ...:     print(i, l[i], l[:i], end='') 
                    ...:     if l[i] in l[:i]: 
                    ...:          print( ': remove', l[i]) 
                    ...:          del l[i] 
                    ...:     else: 
                    ...:          print() 
                    ...: print(l)
                [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
                11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
                [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
                10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
                [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
                9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
                [6, 5, 1, 4, 6, 1, 6, 2, 2]
                8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
                [6, 5, 1, 4, 6, 1, 6, 2]
                7 2 [6, 5, 1, 4, 6, 1, 6]
                [6, 5, 1, 4, 6, 1, 6, 2]
                6 6 [6, 5, 1, 4, 6, 1]: remove 6
                [6, 5, 1, 4, 6, 1, 2]
                5 1 [6, 5, 1, 4, 6]: remove 1
                [6, 5, 1, 4, 6, 2]
                4 6 [6, 5, 1, 4]: remove 6
                [6, 5, 1, 4, 2]
                3 4 [6, 5, 1]
                [6, 5, 1, 4, 2]
                2 1 [6, 5]
                [6, 5, 1, 4, 2]
                1 5 [6]
                [6, 5, 1, 4, 2]
                
                In [94]:                                                                                                                             
                

                【讨论】:

                • 在发布之前,我已经在答案正文中搜索了“地点”,但无济于事。如果其他人以类似的方式解决了问题,请提醒我,我会尽快删除我的答案。
                • 如果您想要就地操作,您可以使用l[:] = &lt;one of the the faster methods&gt;,不是吗?
                • @timgeb 是和否...当我做a=[1]; b=a; a[:]=[2] 然后b==[2] 值是True,我们可以说我们正在就地做,但是你建议使用新空间有一个新列表,用新数据替换旧数据并将旧数据标记为垃圾收集,因为不再被任何东西引用,所以说它就地运行有点延伸了我所展示的概念有可能……效率低吗?是的,但我已经提前告诉过了。
                【解决方案25】:

                消除序列中的重复值,但保留剩余项的顺序。使用通用生成器函数。

                # 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}]
                

                【讨论】:

                  【解决方案26】:

                  zmk 的方法使用非常快的列表理解,但自然地保持顺序。为了应用于区分大小写的字符串,它可以很容易地修改。这也保留了原始案例。

                  def DelDupes(aseq) :
                      seen = set()
                      return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]
                  

                  密切相关的功能是:

                  def HasDupes(aseq) :
                      s = set()
                      return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)
                  
                  def GetDupes(aseq) :
                      s = set()
                      return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))
                  

                  【讨论】:

                    【解决方案27】:

                    这是一个简单的方法:

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

                    给出输出:

                    ["hello", " ", "w", "o", "r", "l", "d"]
                    

                    【讨论】:

                    • 顺便说一句,这里不需要 lambda:sorted(set(list1 ), key=list1.index)
                    【解决方案28】:

                    熊猫用户应该查看pandas.unique

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

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

                    【讨论】:

                    • 不错的一个。我永远无法想象会为此使用 pandas,但它确实有效
                    • list(pd.unique(a)) 会将其转换为 OP 想要的普通列表。赞成熊猫解决方案。从来没有想过这样做。
                    • pd.unique(lst).tolist() 是更好的成语。抄送:@JoeFerndz
                    【解决方案29】:

                    一个线性列表理解:

                    values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]
                    

                    【讨论】:

                      【解决方案30】:

                      感谢@wjandrea 提供 dict.fromdict 方法的想法:

                      def solve(arr): 
                          return list(dict.fromkeys(arr[::-1]))[::-1]
                      

                      这将反转输入和输出以正确迭代

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2013-01-17
                        • 2014-05-21
                        • 2019-02-13
                        • 1970-01-01
                        • 2012-07-21
                        • 1970-01-01
                        相关资源
                        最近更新 更多