优雅地删除列表中连续重复的元素

3 投票
4 回答
693 浏览
提问于 2025-04-17 03:37

我想找一种干净、符合Python风格的方法来从以下列表中去掉:

li = [0, 1, 2, 3, 3, 4, 3, 2, 2, 2, 1, 0, 0]

所有连续重复的元素(也就是出现超过一个的数字),这样才能得到:

re = [0, 1, 2, 4, 3, 1]

虽然我有可以运行的代码,但我觉得它不够Python风格。我很确定一定有其他方法(可能是一些不太为人知的itertools函数?)可以更简洁优雅地实现我想要的效果。

4 个回答

1

在编程中,有时候我们需要把一些东西放到一个地方,然后再从那个地方取出来。这个过程就像把书放到书架上,想要看书的时候再从书架上拿下来。

在代码里,这种放东西和取东西的方式通常叫做“存储”。存储可以有很多种方式,比如用变量、数组或者对象。变量就像一个盒子,我们可以把值放进去,之后再从盒子里拿出来。数组则是一个可以放很多值的盒子,像一个书架,可以放很多本书。而对象则更像是一个有很多不同功能的工具箱,可以存放各种类型的信息。

当我们在写代码的时候,合理地使用这些存储方式,可以让我们的程序更高效,也更容易理解。就像整理书架一样,把书放在合适的位置,找起来才方便。

tmp = [object()] + li + [object()]
re = [y for x, y, z in zip(tmp[2:], tmp[1:-1], tmp[:-2]) if y != x and y != z]
4

agf的回答在处理小组数量不多的情况下很好,但如果连续重复的元素很多,直接不对这些组进行“加1”会更有效率。

[key for key, group in groupby(li) if all(i==0 for i,j in enumerate(group)) ]
8

这里有一个版本,基于 Karl的回答,这个版本不需要复制列表(比如 tmp、切片和压缩后的列表)。在处理大列表时,izip 比(Python 2中的)zip 快很多。chain 的速度稍慢于切片,但不需要 tmp 对象或列表的复制。使用 islice 加上创建一个 tmp 会快一点,但需要更多的内存,而且看起来不够优雅。

from itertools import izip, chain
[y for x, y, z in izip(chain((None, None), li),
                       chain((None,), li),
                       li) if x != y != z]

通过 timeit 测试显示,这个版本的速度大约是Karl的回答或我最快的 groupby 版本的两倍,特别是对于短的组。

如果你的列表可能包含 None,确保使用其他值(比如 object())。

如果你需要在一个不是序列的迭代器/可迭代对象上使用这个版本,或者你的组很长,就用这个:

[key for key, group in groupby(li)
        if (next(group) or True) and next(group, None) is None]

timeit 显示,对于1000个项目的组,这个版本的速度大约是其他版本的十倍。

之前的慢版本:

[key for key, group in groupby(li) if sum(1 for i in group) == 1]
[key for key, group in groupby(li) if len(tuple(group)) == 1]

撰写回答