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

问题描述

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

> a = [1,1,2,3,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,3],以及每个重复组中的项目列表 [<itertools._grouper object ...>,...]。为此,我使用了 the solution in this answer 来使用 zip 来“解压缩”:

>>> keys,values = zip(*duplicates)
>>> print(keys)
(1,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)(2,2)(3,3)

解决方法

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

import itertools

a = [1,1,2,3,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]
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 的文档解决了这个问题:

返回的组本身就是一个迭代器,它与 groupby() 共享底层的可迭代对象。因为源是共享的,所以当 groupby() 对象前进时,之前的组不再可见。因此,如果稍后需要该数据,则应将其存储为列表:

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 来帮助存储列表,而不是使用 defaultdict

from collections import defaultdict

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...