我正在四处玩,以便对itertools groupby
有更好的感觉,因此我按照数字对元组列表进行了分组,并尝试获取结果组的列表。当我将groupby
的结果转换为列表时,我得到一个奇怪的结果:除了最后一组之外的所有组都是空的。这是为什么?我假设将迭代器转换为列表效率较低但从不改变行为。我猜这些列表是空的,因为遍历了内部迭代器,但是何时/何地发生了?
import itertools
l=list(zip([1,2,2,3,3,3],['a','b','c','d','e','f']))
#[(1, 'a'), (2, 'b'), (2, 'c'), (3, 'd'), (3, 'e'), (3, 'f')]
grouped_l = list(itertools.groupby(l, key=lambda x:x[0]))
#[(1, <itertools._grouper at ...>), (2, <itertools._grouper at ...>), (3, <itertools._grouper at ...>)]
[list(x[1]) for x in grouped_l]
[[], [], [(3, 'f')]]
grouped_i = itertools.groupby(l, key=lambda x:x[0])
#<itertools.groupby at ...>
[list(x[1]) for x in grouped_i]
[[(1, 'a')], [(2, 'b'), (2, 'c')], [(3, 'd'), (3, 'e'), (3, 'f')]]
答案 0 :(得分:7)
groupby
超级懒惰。这是一个有启发性的演示。让我们分组三个a
- 值和四个b
- 值,并打印出正在发生的事情:
>>> from itertools import groupby
>>> def letters():
for letter in 'a', 'a', 'a', 'b', 'b', 'b', 'b':
print('yielding', letter)
yield letter
让我们滚动:
>>> groups = groupby(letters())
>>>
还没有打印出来!所以到目前为止,groupby
没有。多么懒惰的屁股。让我们问第一组:
>>> next(groups)
yielding a
('a', <itertools._grouper object at 0x05A16050>)
所以groupby
告诉我们这是一组a
- 值,我们可以通过那个_grouper
对象来获取它们。但是等等,为什么&#34;产生&#34;只打印一次?我们的发电机正在产生三个,不是吗?好吧,那是因为groupby
是懒惰的。它确实将一个值读为识别该组,因为它需要告诉我们该组的内容,即它是{{1}的一组} - 值。 提供我们a
我们要求所有小组成员如果我们想要。但我们没有要求通过会员,所以懒惰的流浪汉没有进一步。它根本没有理由。我们要求下一组:
_grouper
等等,什么?为什么&#34;产生&#34;当我们现在正在处理第二个组时,>>> next(groups)
yielding a
yielding a
yielding b
('b', <itertools._grouper object at 0x05A00FD0>)
组 - 值?好吧,因为b
之前已经在第一个groupby
之后停止了,因为这足以让我们得到我们所要求的一切。但是现在,要告诉我们第二组,它必须找到第二组,为此它会询问我们的生成器,直到它看到除a
之外的其他内容。注意&#34;产生b&#34;再次只打印一次,即使我们的生成器产生四。我们要求第三组:
a
好的,所以没有第三组,因此>>> next(groups)
yielding b
yielding b
yielding b
Traceback (most recent call last):
File "<pyshell#32>", line 1, in <module>
next(groups)
StopIteration
发出groupby
所以消费者(例如,循环或列表理解)会知道停止。但在那之前,剩下的&#34;收益b&#34;得到印刷,因为StopIteration
摆脱了懒惰的屁股并走过剩下的价值,希望找到一个新的团体。
让我们再试一次,这次让我们要求成员:
groupby
同样,>>> groups = groupby(letters())
>>> key, members = next(groups)
yielding a
>>> key
'a'
向我们的生成器询问了一个值,以便识别该组,以便它可以告诉我们它是groupby
- 组。但这次,我们还要求小组成员:
a
啊哈!剩下的&#34;产生了一个&#34;。而且,已经是第一个&#34;屈服的b&#34;!即使我们还没有要求第二组!但当然>>> list(members)
yielding a
yielding a
yielding b
['a', 'a', 'a']
必须走这么远,因为我们要求小组成员,所以它必须继续寻找直到它成为非成员。让我们来看下一组:
groupby
等等,什么?什么都没有印刷? >>> key, members = next(groups)
>>>
正在睡觉吗?醒来!哦等等......那是对的...它已经发现下一组是groupby
- 值。让我们问所有这些:
b
现在剩余三个&#34;产生b&#34;发生了,因为我们要求他们,所以>>> list(members)
yielding b
yielding b
yielding b
['b', 'b', 'b', 'b']
必须得到它们。
让我们用groupby
:
list(groupby(...))
请注意,第一组不仅是空的,而且第二组只有一个元素(你没有提到)。
为什么?
再次:>>> groups = list(groupby(letters()))
yielding a
yielding a
yielding a
yielding b
yielding b
yielding b
yielding b
>>> [list(members) for key, members in groups]
[[], ['b']]
超级懒惰。 为您提供那些groupby
个对象,以便可以遍历每个群组的成员。但是如果你没有要求查看小组成员,而只是要求确定下一个小组,那么_grouper
只是耸耸肩,就像&#34;好吧,你是老板,我只是去寻找下一组&#34;。
groupby
所做的是要求list(groupby(...))
识别所有群组。所以它就是这样做的。但是,如果你最后要求每个小组的成员,那么groupby
就像&#34;老兄......对不起,我把它们提供给你但你没有要他们。而我懒惰,所以我没有任何理由不保留任何东西。我可以给你最后一组的最后一个成员,因为我还记得那一个,但是对于那之前的一切...对不起,我再也没有了,你应该告诉我你想要他们&#34;。
P.S。在所有这一切中,当然&#34;懒惰&#34;真的意味着&#34;高效&#34;。不是坏事,而是好事!
答案 1 :(得分:2)
摘要:原因是itertools通常不存储数据。他们只使用迭代器。因此,当外迭代器前进时,内迭代器也必须。
打个比方:想象一下,你是一名站在门口的空乘人员,允许单行乘客上飞机。乘客由登机组安排,但您一次只能看到并接纳一名乘客。定期,当人们进入你时,你会知道一个登机组何时结束然后接下来已经开始。
要进入下一组,您将不得不接纳当前组中的所有剩余乘客。在不让所有现有乘客通过的情况下,你无法看到排队的下游。
Unix比较: groupby()的设计在算法上类似于Unix uniq实用程序。
文档说的是什么:&#34;返回的组本身就是一个迭代器,它与groupby()共享底层的iterable。因为源是共享的,所以当groupby()对象被提前时,前一个组不再可见。&#34;
如何使用:如果以后需要数据,则应将其存储为列表:
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)
答案 2 :(得分:1)
来自itertools.groupby()
documentation:
返回的组本身就是一个迭代器,它与
groupby()
共享底层的iterable。由于源是共享的,因此当groupby()
对象前进时,前一个组将不再可见。
将groupby()
的输出转换为列表会使groupby()
对象前进。
因此,您不应该将itertools.groupby
对象列入类型。如果您想将值存储为list
,那么您应该执行类似 list comprehension 的操作,以便创建groupby
对象的副本:
grouped_l = [(a, list(b)) for a, b in itertools.groupby(l, key=lambda x:x[0])]
这将允许您多次迭代列表(从groupby
对象转换)。但是,如果您只对迭代结果一次感兴趣,那么您在问题中提到的第二个解决方案就足以满足您的要求。