【Python 秘籍】从序列中移除重复项且保持元素间顺序不变

问题

我们想去除序列中出现的重复元素,但仍然保持剩下的元素顺序不变。

解决方案

这个问题可以通过使用列表和生成器轻松解决。示例如下:

def dedupe(items):
    seen = []
    for item in items:
        if item not in seen:
            yield item
            seen.append(item)

下面是如何使用这个函数的例子:

>>> a = [1, 5, 2, 1, 9, 1, 5, 10]
>>> list(dedupe(a))
[1, 5, 2, 9, 10]
>>> b = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
>>> list(dedupe(b))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

对于以上序列元素为不可哈希对象的例子中,如果我们只想看元素中的某个键是否重复,可以对上述代码稍作修改:

def dedupe(items, key=None):
    seen = []
    for item in items:
        val = item if not key else key(item)
        if val not in seen:
            yield item
            seen.append(val)

相应的用法如下:

>>> list(dedupe(b, key=lambda d: d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]

讨论

如果想要做的只是去除重复项,那么通常足够简单的办法就是构建一个集合。例如:

>>> a
[1, 5, 2, 1, 9, 1, 5, 10]
>>> set(a)
[1, 2, 5, 9, 10]

但是这种方法不能保证元素间的顺序不变(因为集合是无序容器),因此得到的结果会被打乱。前面展示的解决方案可避免出现这个问题。

另外,这个函数的通用化很强——不必绑定在只能对列表进行处理。比如,如果想读一个文件,去除其中重复的文本行,只需这样处理:

with open(somefile, 'r') as f:
    for line in dedupe(f):
        ...