itertools.groupby:按对逐组迭代

2 投票
2 回答
1974 浏览
提问于 2025-04-17 15:53

我该如何成对地遍历groupby的结果呢?我尝试的方法似乎不太奏效:

from itertools import groupby,izip

groups = groupby([(1,2,3),(1,2),(1,2),(3,4,5),(3,4)],key=len)

def grouped(iterable, n):    
    return izip(*[iterable]*n)

for g, gg in grouped(groups,2):
    print list(g[1]), list(gg[1])

我得到的输出是:

[] [(1, 2), (1, 2)]
[] [(3, 4)]

我想要的输出是:

[(1, 2, 3)] [(1, 2), (1, 2)]
[(3, 4, 5)] [(3, 4)]

2 个回答

2

当你尝试查看groupby的第二个键时,其实是在强迫它深入到源迭代器中去获取数据。因为通常没有地方可以存储第一个组的项目,所以这些项目就被丢弃了。

所以现在我们明白了,在查看第二组的键(或者项目)之前,我们需要确保已经存储了第一组的项目。

有些人可能会对此感到不满,但

>>> groups = groupby([(1, 2, 3), (1, 2), (1, 2), (3, 4, 5), (3, 4)], key=len)
>>> for i, j in ((list(i[1]), list(next(groups)[1])) for i in groups):
...     print i, j
... 
[(1, 2, 3)] [(1, 2), (1, 2)]
[(3, 4, 5)] [(3, 4)]
3
import itertools as IT

groups = IT.groupby([(1,2,3),(1,2),(1,2),(3,4,5),(3,4)], key=len)
groups = (list(group) for key, group in groups)

def grouped(iterable, n):
    return IT.izip(*[iterable]*n)

for p1, p2  in grouped(groups, 2):
    print p1, p2
[(1,2,3),(1,2),(1,2),(3,4,5),(3,4)]

产生

[(1, 2, 3)] [(1, 2), (1, 2)]
[(3, 4, 5)] [(3, 4)]

你发的代码很有意思。它有一个普通的问题和一个微妙的问题。

普通的问题是,itertools.groupby 返回的是一个迭代器,每次迭代时都会输出一个键和一个组。因为你只对组感兴趣,而不关心键,所以你需要类似这样的东西:

groups = (group for key, group in groups)

微妙的问题就比较难解释了——我也不太确定自己完全理解。我的猜测是:groupby 返回的迭代器把输入变成了一个迭代器。这个 groupby 迭代器就像是把底层数据迭代器包裹起来,类似于 csv.reader 是如何包裹一个底层文件对象迭代器的。你只能通过这个迭代器走一遍,只有这一遍。在配对 groups 中的项目时,itertools.izip 函数会让 groups 迭代器从第一个项目推进到第二个。因为你只能走一遍迭代器,所以第一个项目已经被消耗掉了,因此当你调用 list(g[1]) 时,它是空的。

一个不太令人满意的解决办法是把 groups 中的迭代器转换成列表:

groups = (list(group) for key, group in groups)

这样 itertools.izip 就不会提前消耗它们。编辑:再想想,这个解决办法也不是那么糟糕。groups 仍然是一个迭代器,只有在被消耗时才会把 group 转换成列表。

撰写回答