【问题标题】:How do I remove identical items from a list and sort it in Python?如何从列表中删除相同的项目并在 Python 中对其进行排序?
【发布时间】:2014-05-09 13:57:35
【问题描述】:

如何以最佳方式从列表中删除相同的项目并在 Python 中对其进行排序?

假设我有一个清单:

my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']

我可以遍历列表的副本(因为您不应该在遍历列表时更改列表),逐个项目,并删除除一个之外的所有项目:

for item in my_list[:]: # must iterate over a copy because mutating it
    count = my_list.count(item) # see how many are in the list
    if count > 1:
        for _ in range(count-1): # remove all but one of the element
            my_list.remove(item)

去掉多余的项:

['b', 'c', 'a', 'd', 'f', 'e']

然后对列表进行排序:

my_list.sort()

所以 my_list 现在是:

['a', 'b', 'c', 'd', 'e', 'f']

但是,删除相同元素并对该列表进行排序的最有效和最直接(即高效)的方法是什么?

*这个问题是在工作中提出的(我非常想回答这个问题,但我们最资深的 Python 开发人员之一比我先解决了这个问题),我还在当地的 Python Meetup 小组中提出了这个问题,很少有人对此有好的答案,so I'm answering it Q&A style, as suggested by Stackoverflow

【问题讨论】:

  • 很好的资源。许多问题的完美“参考”。

标签: python performance list sorting set


【解决方案1】:

从列表中删除冗余元素的最佳方法是将其转换为一个集合,并且由于 sorted 接受任何可迭代并返回一个列表,这比分段执行要高效得多。

my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']

def sorted_set(a_list):
    return sorted(set(a_list))

new_list = sorted_set(my_list)

new_list 是:

['a', 'b', 'c', 'd', 'e', 'f']

这种方法的缺点是给 set 的元素必须是可散列的,所以如果元素不可散列,你会得到一个错误:

>>> my_list = [['a'], ['a'], ['b'], ['c']]
>>> sorted(set(my_list))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

这种简单的情况可以通过将子列表转换为元组来解决,这可能比答案中的解决方案性能更高,这可能意味着更昂贵的相等性测试:

>>> my_list = [tuple(i) for i in my_list]
>>> sorted(set(my_list))
[('a',), ('b',), ('c',)]

但其他情况需要找到不同的解决方法。对于其他解决方案,这不是必需的(但同样,计算成本可能要高得多):

def remove_extras_and_sort(my_list):
    for item in my_list[:]:
        count = my_list.count(item)
        if count > 1:
            for _ in range(count-1):
                my_list.remove(item)
    my_list.sort()
    return my_list

这适用于子列表:

>>> my_list = [['a'], ['a'], ['b'], ['c']]
>>> remove_extras_and_sort(my_list)
[['a'], ['b'], ['c']]

比较性能:

import timeit

setup = '''
my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']
def remove_extras_and_sort(my_list):
    for item in my_list[:]:
        count = my_list.count(item)
        if count > 1:
            for _ in range(count-1):
                my_list.remove(item)
    my_list.sort()
    return my_list

def sorted_set(a_list):
    return sorted(set(a_list))
'''

timeit.timeit('sorted_set(my_list[:])', setup=setup)
timeit.timeit('remove_extras_and_sort(my_list[:])', setup=setup)

当我在我的系统上分别测量它们时,它会返回时间:

1.5562372207641602
4.558010101318359

这意味着考虑到每次复制列表的必要开销(如果我们不复制列表,我们只会对一个已经排序的列表,因为设置只运行一次)。


我们可以反汇编每个函数:

import dis

def remove_extras_and_sort(my_list):
    for item in my_list[:]:
        count = my_list.count(item)
        if count > 1:
            for _ in range(count-1):
                my_list.remove(item)
    my_list.sort()
    return my_list

def sorted_set(a_list):
    return sorted(set(a_list))

并且仅仅通过查看输出,我们看到第一个函数的字节码长度是原来的六倍多:

>>> dis.dis(remove_extras_and_sort)
  2           0 SETUP_LOOP              85 (to 88)
              3 LOAD_FAST                0 (my_list)
              6 SLICE+0             
              7 GET_ITER            
        >>    8 FOR_ITER                76 (to 87)
             11 STORE_FAST               1 (item)

  3          14 LOAD_FAST                0 (my_list)
             17 LOAD_ATTR                0 (count)
             20 LOAD_FAST                1 (item)
             23 CALL_FUNCTION            1
             26 STORE_FAST               2 (count)

  4          29 LOAD_FAST                2 (count)
             32 LOAD_CONST               1 (1)
             35 COMPARE_OP               4 (>)
             38 POP_JUMP_IF_FALSE        8

  5          41 SETUP_LOOP              40 (to 84)
             44 LOAD_GLOBAL              1 (range)
             47 LOAD_FAST                2 (count)
             50 LOAD_CONST               1 (1)
             53 BINARY_SUBTRACT     
             54 CALL_FUNCTION            1
             57 GET_ITER            
        >>   58 FOR_ITER                19 (to 80)
             61 STORE_FAST               3 (_)

  6          64 LOAD_FAST                0 (my_list)
             67 LOAD_ATTR                2 (remove)
             70 LOAD_FAST                1 (item)
             73 CALL_FUNCTION            1
             76 POP_TOP             
             77 JUMP_ABSOLUTE           58
        >>   80 POP_BLOCK           
             81 JUMP_ABSOLUTE            8
        >>   84 JUMP_ABSOLUTE            8
        >>   87 POP_BLOCK           

  7     >>   88 LOAD_FAST                0 (my_list)
             91 LOAD_ATTR                3 (sort)
             94 CALL_FUNCTION            0
             97 POP_TOP             

  8          98 LOAD_FAST                0 (my_list)
            101 RETURN_VALUE        

而且推荐的方式有更短的字节码:

>>> dis.dis(sorted_set)
  2           0 LOAD_GLOBAL              0 (sorted)
              3 LOAD_GLOBAL              1 (set)
              6 LOAD_FAST                0 (a_list)
              9 CALL_FUNCTION            1
             12 CALL_FUNCTION            1
             15 RETURN_VALUE        

因此,我们看到使用 Python 的内置功能比尝试重新发明轮子更有效和高效。


附录:需要更充分探索的其他选项:

def groupby_sorted(my_list):
    """if items in my_list are unhashable"""
    from itertools import groupby
    return [e for e, g in groupby(sorted(my_list))]

def preserve_order_encountered(my_list):
    """elements in argument must be hashable - preserves order encountered"""
    from collections import OrderedDict
    return list(OrderedDict.fromkeys(my_list))

【讨论】:

    【解决方案2】:
    my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']
    b=[]
    for x in my_list:
        try:
           z=b.index(x)
        except:
           b.append(x)
    
    
    b.sort()
    output
    ['a', 'b', 'c', 'd', 'e', 'f']
    

    【讨论】:

    • 请为您的解决方案添加更多解释。这可能很明显,但仅仅一个代码 sn -p 是不够的。
    【解决方案3】:

    将项目放入一个集合然后排序会很有效,但它确实依赖于可散列的项目:

    def sorted_set(a_list):
        return sorted(set(a_list))
    
    timeit sorted_set(my_list)
    100000 loops, best of 3: 3.19 µs per loop
    

    获取唯一元素的排序列表的经典方法是首先排序,然后对列表执行第二次遍历,消除相同的元素(在排序后保证相邻):

    def sorted_unique(a_list):
        l = sorted(a_list)
        return l[:1] + [b for a, b in zip(l, l[1:]) if a != b]
    

    与使用set相比,这还不算太糟糕:

    timeit sorted_unique(my_list)
    100000 loops, best of 3: 6.6 µs per loop
    

    我们实际上可以使用itertools.groupby 做得更好:

    def sorted_group(a_list):
        return [k for k, _ in groupby(sorted(a_list))]
    
    timeit sorted_group(my_list)
    100000 loops, best of 3: 5.3 µs per loop
    

    最后,如果项目是原始值,那么值得考虑 numpy;在这种情况下(在一个小列表中)开销大于任何好处,但它在较大的问题集上表现良好:

    def sorted_np(a_list):
        return np.unique(np.sort(a_list))
    
    timeit sorted_np(my_list)
    10000 loops, best of 3: 42 µs per loop
    
    my_list = [random.randint(0, 10**6) for _ in range(10**6)]
    
    timeit sorted_set(my_list)
    1 loops, best of 3: 454 ms per loop
    
    timeit sorted_np(my_list)
    1 loops, best of 3: 333 ms per loop
    

    【讨论】:

      【解决方案4】:

      这是python中的两个简单函数之一:

      my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']
      print sorted(set(my_list))
      

      你得到你想要的;)

      如果您想了解有关集合的更多信息,请查看 here,以及有关在 python 中排序的更多信息,请查看 here

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-12-03
        • 2014-11-05
        • 2011-02-03
        • 2012-09-20
        • 1970-01-01
        • 1970-01-01
        • 2021-03-15
        • 2017-10-09
        相关资源
        最近更新 更多