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

有没有一种内置的方法可以从Python中的列表中删除重复项,同时保持顺序? 我知道我可以使用一套删除重复项,但破坏了原来的顺序。 我也知道我可以像这样推出自己的产品:

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

(感谢解放这个代码示例。)

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

相关问题:在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分配给seen_add而不是只调用seen.add ? Python是一种动态语言,可以解决seen.add每次迭代都比解析局部变量更昂贵。 seen.add在迭代之间可能会发生变化,并且运行时不够聪明以排除这种情况。 为了安全起见,它必须每次检查对象。

如果你打算在同一个数据集上使用这个函数,也许你会更好地使用一个有序集合:http://code.activestate.com/recipes/528878/

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


编辑2016年

正如Raymond指出的那样,在OrderedDict在C中实现的python 3.5+中,列表理解方法将比OrderedDict慢(除非实际上最后需要列表 - 即使这样,只有在输入非常短的情况下)。 所以3.5+的最佳解决方案是OrderedDict

重要编辑2015年

正如@abarnert所指出的, more_itertools库( pip install more_itertools )包含一个unique_everseen函数,它可以解决这个问题,而列表not seen.add没有任何不可读的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

在Python 2.7+ ,接受的常用成语(它的工作原理,但并未针对速度进行优化,现在我将使用unique_everseen )为此使用collections.OrderedDict

运行时间: O(N)

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

这看起来比以下更好:

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

并没有利用丑陋的黑客

not seen.add(x)

这依赖于set.add是一个始终返回None的就地方法,因此not None评估为True

但请注意,尽管它具有相同的运行时复杂度O(N),但解决方案在原始速度上更快。


在Python 2.7中 ,从迭代中移除重复的新方法同时保持原始顺序:

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

在Python 3.5中 ,OrderedDict有一个C实现。 我的计时表明,现在这是Python 3.5各种方法中速度最快,最短的一种。

在Python 3.6中 ,常规字典变得既有序又紧凑。 (这个特性适用于CPython和PyPy,但在其他实现中可能不存在)。 这为我们提供了一种新的最快捷的重复数据删除方式,同时保留订单

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

在Python 3.7中 ,常规字典保证在所有实现中都有序。 所以,最短和最快的解决方案是:

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

对@max的回应:一旦你移动到3.6或3.7,并使用常规词典而不是OrderedDict,你无法以其他方式真正击败游戏。 字典很密集,很容易转换成列表,几乎没有开销。 目标列表预先调整为len(d),该列表保存在列表理解中发生的所有调整大小。 另外,由于内部密钥列表是密集的,所以复制指针几乎快速地作为列表副本。

链接地址: http://www.djcxy.com/p/18773.html

上一篇: How do you remove duplicates from a list whilst preserving order?

下一篇: Type List vs type ArrayList in Java