删除列表中连续重复元素的优雅方法[关闭]

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) zipchain 比切片稍慢,但不需要 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 短组答案的两倍。

如果您的列表可以包含Nones,请确保使用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 的列表,一个包含多个唯一 objects 的列表,以及一个包含多个相同 objects 的列表。 @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 的速度很快,但在它们不能完全满足您的需求的情况下,自定义生成器似乎仍然表现最好。

以上是关于删除列表中连续重复元素的优雅方法[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

从 2D 列表中删除连续重复项,python?

如何找到没有重复数字的元素? [关闭]

如何删除已经在list中的重复项

从列表中删除布尔值[重复]

在 Java 中,有没有更优雅的方法从字符串的 ArrayList 中删除重复的字符串?

如何有效地删除列表列表中的连续重复项?