对 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
进行索引。
我在使用 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 thegroupby()
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
进行索引。