如何以最符合Python风格的方式识别列表中连续的重复项?

41
我有一个整数列表,我想识别连续的重复块:也就是说,我想生成一个保持顺序的元组列表,每个元组包含(待检查的整数,出现次数)。
例如,如果我有一个列表如下:
[0, 0, 0, 3, 3, 2, 5, 2, 6, 6]

我希望得到的结果是:

[(0, 3), (3, 2), (2, 1), (5, 1), (2, 1), (6, 2)]

我有一个相对简单的方法,使用for循环、一个临时变量和一个计数器:

result_list = []
current = source_list[0]
count = 0
for value in source_list:
    if value == current:
        count += 1
    else:
        result_list.append((current, count))
        current = value
        count = 1
result_list.append((current, count))

但我非常喜欢Python的函数式编程习惯,并且我希望能够使用简单的生成器表达式来实现。然而,当使用生成器时很难保留子计数。我有一种感觉,两步法可以帮我做到这一点,但现在我卡住了。

有没有特别优雅/Pythonic的方法来实现这一点,特别是使用生成器?


10
参考文献中的这个过程被称为: http://en.wikipedia.org/wiki/Run-length_encoding - Aaron Robson
1个回答

80
>>> from itertools import groupby
>>> L = [0, 0, 0, 3, 3, 2, 5, 2, 6, 6]
>>> grouped_L = [(k, sum(1 for i in g)) for k,g in groupby(L)]
>>> # Or (k, len(list(g))), but that creates an intermediate list
>>> grouped_L
[(0, 3), (3, 2), (2, 1), (5, 1), (2, 1), (6, 2)]

就像他们说的那样,Python 已经准备好了所有需要的工具。

来自 JBernardo 的建议:使用sum和生成器表达式,请参见评论。


10
或许你可以将 len(list(g)) 改为 sum(1 for i in g),避免使用中间存储。 - JBernardo
2
@JBernardo:好建议,谢谢。当我使用groupby时,从g创建列表总是让我感到有些困扰。 - jscs
@JBernardo:实际上我会选择创建中间列表。虽然做求和可能更有效率,但我认为前者更易读(确切地说明了我们想要发生的事情),因此更符合Pythonic!我确实认为这个“加一”的解决方案暗示着生成器中缺少某些东西,特别是没有内置函数明确告诉将生成多少元素。这在未来可能会得到修正吗? - machine yearning
2
@机器人:原则上是不可能的。考虑以下代码:def long_gen(): while True: yield 1 这个迭代器的长度是多少?请参见:https://dev59.com/z3RC5IYBdhLWcg3wKtv2 - jscs
@Josh:我明白了,如果这是一个已经成熟的习语,那么我会收回之前对Pythonic性的评论。我想停机问题可能会对任何通用情况下的修改造成困扰。感谢您深思熟虑的回答! - machine yearning
1
@machine:不用谢。我在其他地方也看到过sum的使用,但没想到可以在这种情况下使用它。我认为大多数读者都能很快理解它的含义。 - jscs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接