对 itertools.groupby 的结果使用 zip 意外给出空列表

Using zip on the results of itertools.groupby unexpectedly gives empty lists

我在使用 zip 转置 itertools.groupby 的结果时遇到了一些意外的空列表。实际上我的数据是一堆对象,但为了简单起见,假设我的起始数据是这个列表:

> a = [1, 1, 1, 2, 1, 3, 3, 2, 1]

我想对重复项进行分组,所以我使用 itertools.groupby(先排序,否则 groupby 只会对连续的重复项进行分组):

from itertools import groupby
duplicates = groupby(sorted(a))

这给出了一个 itertools.groupby 对象,当转换为列表时给出

[(1, <itertools._grouper object at 0x7fb3fdd86850>), (2, <itertools._grouper object at 0x7fb3fdd91700>), (3, <itertools._grouper object at 0x7fb3fdce7430>)]

到目前为止,还不错。但现在我想转置结果,所以我有一个唯一值列表,[1, 2, 3],以及每个重复组中的项目列表,[<itertools._grouper object ...>, ...]。为此,我使用 the solution in this answer 使用 zip 来“解压缩”:

>>> keys, values = zip(*duplicates)
>>> print(keys)
(1, 2, 3)
>>> print(values)
(<itertools._grouper object at 0x7fb3fdd37940>, <itertools._grouper object at 0x7fb3fddfb040>, <itertools._grouper object at 0x7fb3fddfb250>)

但是当我尝试读取 itertools._grouper 对象时,我得到了一堆空列表:

>>> for value in values:
...    print(list(value))
...
[]
[]
[]

这是怎么回事?每个 value 不应该包含原始列表中的重复项,即 (1, 1, 1, 1, 1)(2, 2)(3, 3)

要按每个唯一键进行分组以进行重复处理:

import itertools

a = [1, 1, 1, 2, 1, 3, 3, 2, 1]
g1 = itertools.groupby(sorted(a))
for k,v in g1:
    print(f"Key {k} has", end=" ")
    for e in v:
        print(e, end=" ")
    print()
# Key 1 has 1 1 1 1 1 
# Key 2 has 2 2 
# Key 3 has 3 3 

如果只是计算数量,最小排序:

import itertools
import collections

a = [1, 1, 1, 2, 1, 3, 3, 2, 1]
g1 = itertools.groupby(a)
c1 = collections.Counter()
for k,v in g1:
    l = len(tuple(v))
    c1[k] += l
for k,v in c1.items():
    print(f"Element {k} repeated {v} times")
# Element 1 repeated 5 times
# Element 2 repeated 2 times
# Element 3 repeated 2 times

啊。多个迭代器都使用相同的底层对象的美妙之处。

groupby 的文档解决了这个问题:

The returned group is itself an iterator that shares the underlying iterable with groupby(). Because the source is shared, when the groupby() object is advanced, the previous group is no longer visible. So, if that data is needed later, it should be stored as a list:

groups = []
uniquekeys = []
data = sorted(data, key=keyfunc)
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

所以最终发生的是所有 itertools._grouper 对象在你解包之前就被消耗掉了。如果您尝试多次重复使用任何其他迭代器,您会看到类似的效果。如果您想更好地理解,请查看文档中的下一段,其中显示了 groupby 的内部结构实际如何工作。

帮助我理解这一点的部分原因是使用更明显不可重用的迭代器(如文件对象)来处理示例。它有助于脱离您可以跟踪的底层缓冲区的想法。

一个简单的解决方法是自己使用对象,正如文档所建议的那样:

# This is an iterator over a list:
duplicates = groupby(sorted(a))

# If you convert duplicates to a list, you consume it

# Don't store _grouper objects: consume them yourself:
keys, values = zip(*((key, list(value)) for key, value in duplicates)

正如另一个答案所暗示的那样,您不需要涉及排序的 O(N log N) 解决方案,因为您可以在 O(N) 时间内一次性完成此操作。而不是使用 Counter, though, I'd recommend a defaultdict 来帮助存储列表:

from collections import defaultdict

result = defaultdict(list)
for item in a:
    result[item].append(item)

对于更复杂的对象,您将使用 key(item) 而不是 item 进行索引。