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

Aar*_*all 5 python sorting performance list set

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

说我有一个清单:

my_list = ['a', 'a', 'b', 'c', 'd', 'a', 'e', 'd', 'f', 'e']
Run Code Online (Sandbox Code Playgroud)

我可以遍历列表的副本(因为你不应该在迭代时改变列表),item for item,并删除所有项目,除了一个:

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)
Run Code Online (Sandbox Code Playgroud)

这删除了多余的项目:

['b', 'c', 'a', 'd', 'f', 'e']
Run Code Online (Sandbox Code Playgroud)

然后对列表进行排序:

my_list.sort()
Run Code Online (Sandbox Code Playgroud)

所以my_list现在是:

['a', 'b', 'c', 'd', 'e', 'f']
Run Code Online (Sandbox Code Playgroud)

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

*这个问题出现在工作中(我非常想回答这个问题,但我们的一位资深大多数Python开发人员在我之前就已经开始讨论了这个问题),而且我还在我当地的Python Meetup小组提出了这个问题,很少有人有这样的问题.很好的答案,所以我正在回答它的问答风格,正如Stackoverflow所建议的那样.

Aar*_*all 15

从列表中删除冗余元素的最佳方法是将其转换为集合,并且由于sorted接受任何iterable并返回列表,因此这比分段执行更有效.

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)
Run Code Online (Sandbox Code Playgroud)

和new_list是:

['a', 'b', 'c', 'd', 'e', 'f']
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点是赋予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'
Run Code Online (Sandbox Code Playgroud)

这个简单的案例可以通过将子列表作为元组进行处理来解决,这可能比答案中的解决方案更高效,这可能意味着更昂贵的相等测试:

>>> my_list = [tuple(i) for i in my_list]
>>> sorted(set(my_list))
[('a',), ('b',), ('c',)]
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

适用于子列表:

>>> my_list = [['a'], ['a'], ['b'], ['c']]
>>> remove_extras_and_sort(my_list)
[['a'], ['b'], ['c']]
Run Code Online (Sandbox Code Playgroud)

要比较性能:

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)
Run Code Online (Sandbox Code Playgroud)

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

1.5562372207641602
4.558010101318359
Run Code Online (Sandbox Code Playgroud)

这意味着问题中给出的方法可能花费的时间是计算时间的3倍,因为每次复制列表需要花费必要的开销(如果我们不复制列表,我们只需要排序已经列出的列表已经排序,因为设置只运行一次).


我们可以拆解每个功能:

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))
Run Code Online (Sandbox Code Playgroud)

只需查看输出,我们就会看到第一个函数的字节码长度超过六倍:

>>> 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        
Run Code Online (Sandbox Code Playgroud)

推荐的方法有更短的字节码:

>>> 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        
Run Code Online (Sandbox Code Playgroud)

因此,我们看到使用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))
Run Code Online (Sandbox Code Playgroud)