删除列表中连续重复元素的优雅方法[关闭]
Posted
技术标签:
【中文标题】删除列表中连续重复元素的优雅方法[关闭]【英文标题】:Elegant way to remove contiguous repeated elements in a list [closed] 【发布时间】:2011-11-30 07:32:58 【问题描述】:我正在寻找一种干净的 Pythonic 方法来从以下列表中删除:
li = [0, 1, 2, 3, 3, 4, 3, 2, 2, 2, 1, 0, 0]
所有连续重复的元素(运行时间超过一个数字)从而得到:
re = [0, 1, 2, 4, 3, 1]
但是虽然我有工作代码,但它感觉不像 Pythonic,我很确定肯定有办法(也许是一些鲜为人知的 itertools
函数?)以更简洁和优雅的方式实现我想要的方式。
【问题讨论】:
看起来你的结果应该是:re = [0, 1, 2, 3, 4, 3, 2, 1, 0]
?
@Justin 他想消除长度 > 1 的组。
【参考方案1】:
这是一个基于 Karl's answer 的版本,它不需要列表的副本(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
s,请确保使用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]
【讨论】:
@JBernardo 编辑为使用该方法,因为快速的timeit
表示它快了三分之一,并且在group
很大的情况下您没有额外的内存使用。谢谢。
@JBernardo 对于短组(以及长组),新版本似乎比sum
更快,因为它不需要为每个组创建生成器对象。
我很佩服你在这里坚持不懈的决心。
@KarlKnechtel 我还意识到我从来没有测试过你的长组版本——1000 个项目组比我的groupby
/ next
版本慢 10 倍。【参考方案2】:
agf's answer 如果组的大小很小,则很好,但如果连续有足够多的重复项,则不对这些组“求和 1”会更有效
[key for key, group in groupby(li) if all(i==0 for i,j in enumerate(group)) ]
【讨论】:
这对于长组来说是一个很好的优化,但对于短组来说会慢 50%。我添加了一个版本,对于长组或短组来说似乎更快。【参考方案3】: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]
【讨论】:
我仍在尝试看看这是一个笑话还是一个非常糟糕的解决方案......顺便说一句,它只适用于某些列表 删除重复值组相当于保留非重复值,即与相邻值不同的值。tmp
在两端都有哨兵,将 false 与其他所有内容进行比较。我做了三个列表:中间的一个和原来的一样,其他的每个方向偏移1。因此,当我zip
他们逐元素比较它们时,相当于将原始列表中的每个元素与其两个邻居进行比较,并保留与任一邻居不同的元素。
@JBernardo 这不仅是一个完全严肃的解决方案,而且是我立即想到的方法。 (实际上,我最初开发了一些更简单的东西,留下了重复元素的唯一副本,然后不得不重新阅读规范......)我想看看你的示例列表,它不起作用。在我的测试中,它适用于一个空列表,一个包含一个 object
的列表,一个包含多个唯一 object
s 的列表,以及一个包含多个相同 object
s 的列表。
@KarlKnechtel 是的,我明白了(虽然我看到 JBernardo 没有:P),但是你的答案会更好,有一个解释(将它编辑到你的答案中?)。另外,请注意您可以使用x != y != z
。
@mac 我编写了一个使用itertools
的混合版本,以避免制作所需的列表的许多副本。结果证明它比这个版本或我最快的groupby
版本更快。请参阅我编辑的答案。 Karl - 手动进行比较结果证明是最快的方法,尽管在测试之前它对我来说似乎“天真”。【参考方案4】:
其他解决方案正在使用各种 itertools 助手和理解,并且可能看起来更“Pythonic”。然而,我运行的一个快速计时测试表明这个生成器要快一点:
_undef = object()
def itersingles(source):
cur = _undef
dup = True
for elem in source:
if dup:
if elem != cur:
cur = elem
dup = False
else:
if elem == cur:
dup = True
else:
yield cur
cur = elem
if not dup:
yield cur
source = [0, 1, 2, 3, 3, 4, 3, 2, 2, 2, 1, 0, 0]
result = list(itersingles(source))
【讨论】:
他反复说他在问题中寻找“简洁”、“优雅”和“Pythonic”。即使没有,也不值得为了节省一点时间而维护更多代码,除非您知道这是您的性能瓶颈。 我猜我对“Pythonic”的审美和定义可能与其他人不匹配。就个人而言,我喜欢一个简单的生成器,它可以快速移动一个可迭代的、沿途保持的状态;这不是我可以在许多其他语言中轻松完成的事情,而在 Python 中却很容易表达。其他解决方案之一的“sum(1 for i in group) == 1”对我来说尤其浪费(尽管我不知道有更好的方法来实现这一点)。不过,我同意您的速度与可维护性点 - 如果不是关键点,那么 2 倍的加速是不值得的。sum
是对的。受该观察和 gnibbler 版本的启发,我提出了一个似乎对于短列表和长列表都很快的版本,同时仍然利用 groupby
来保持我的自定义代码最少。
这与我尝试过的一种实现方式比较相似。不完全是我想要的,但似乎您的 cmets 促成了我将接受的答案。谢谢和+1! :)
我使用islice
和临时列表的最新解决方案版本仅比此慢25%,而我选择的chain
版本仅慢50%。我认为itertools
的速度很快,但在它们不能完全满足您的需求的情况下,自定义生成器似乎仍然表现最好。以上是关于删除列表中连续重复元素的优雅方法[关闭]的主要内容,如果未能解决你的问题,请参考以下文章