pee*_*eer 0 python grouping iterator python-itertools
我正在四处玩groupby
,以便对itertools有一个更好的感觉,因此我按照数字对元组列表进行了分组,并尝试获取结果组的列表.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')]]
Run Code Online (Sandbox Code Playgroud)
groupby
是超级懒惰.这是一个有启发性的演示.让我们将三个值a
和四个值组合b
在一起,打印出正在发生的事情:
>>> from itertools import groupby
>>> def letters():
for letter in 'a', 'a', 'a', 'b', 'b', 'b', 'b':
print('yielding', letter)
yield letter
Run Code Online (Sandbox Code Playgroud)
来吧:
>>> groups = groupby(letters())
>>>
Run Code Online (Sandbox Code Playgroud)
什么都没打印好了!所以直到现在,什么groupby
也没做.多么懒惰的屁股.让我们问第一组:
>>> next(groups)
yielding a
('a', <itertools._grouper object at 0x05A16050>)
Run Code Online (Sandbox Code Playgroud)
所以groupby
告诉我们这是一组值a
,我们可以通过该_grouper
对象来获取它们.但是等等,为什么"屈服"只打印一次?我们的发电机正在产生三个,不是吗?好吧,那是因为groupby
懒惰.它确实读了一个值来识别该组,因为它需要告诉我们该组的内容,即它是一组值a
.如果我们愿意的话,它为我们提供了_grouper
让我们获得所有团队成员的目标.但我们没有要求通过会员,所以懒惰的流浪汉没有再进一步.它根本就没有理由.让我们问下一组:
>>> next(groups)
yielding a
yielding a
yielding b
('b', <itertools._grouper object at 0x05A00FD0>)
Run Code Online (Sandbox Code Playgroud)
等等,什么?当我们现在处理第二组时,为什么"屈服于" b
?好吧,因为groupby
之前在第一次之后就停了下来a
因为这足以让我们所有人都要求了.但是现在,要告诉我们关于第二组,它必须找到第二组,为此它询问我们的发电机,直到它看到除了之外的其他东西a
.请注意,"屈服b"再次只打印一次,即使我们的发生器产生其中四个.我们要问第三组:
>>> next(groups)
yielding b
yielding b
yielding b
Traceback (most recent call last):
File "<pyshell#32>", line 1, in <module>
next(groups)
StopIteration
Run Code Online (Sandbox Code Playgroud)
好的,所以没有第三组,因此groupby
发布StopIteration
消费者(例如,循环或列表理解)会知道停止.但在此之前,剩余的"屈服b"得到印刷,因为groupby
摆脱了懒惰的屁股并走过剩下的价值,希望找到一个新的团体.
让我们再试一次,这次让我们问一下成员:
>>> groups = groupby(letters())
>>> key, members = next(groups)
yielding a
>>> key
'a'
Run Code Online (Sandbox Code Playgroud)
再次,groupby
询问我们的生成器只有一个值,以便识别该组,以便它可以告诉我们它是一个a
组.但这一次,我们还会要求小组成员:
>>> list(members)
yielding a
yielding a
yielding b
['a', 'a', 'a']
Run Code Online (Sandbox Code Playgroud)
啊哈!还有剩下的"让步".而且,已经是第一个"屈服的b"了!即使我们甚至没有要求第二组!但当然groupby
要走这么远,因为我们要求小组成员,所以它必须继续寻找直到它成为非成员.让我们来看下一组:
>>> key, members = next(groups)
>>>
Run Code Online (Sandbox Code Playgroud)
等等,什么?什么都没有印刷?在groupby
睡觉吗?醒来!哦等等......这是对的...它已经发现下一组是 - 值b
.让我们问他们所有人:
>>> list(members)
yielding b
yielding b
yielding b
['b', 'b', 'b', 'b']
Run Code Online (Sandbox Code Playgroud)
现在剩下的三个"屈服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']]
Run Code Online (Sandbox Code Playgroud)
请注意,第一组不仅是空的,而且第二组只有一个元素(您没有提到).
为什么?
再说一次:groupby
超级懒惰.它为您提供这些_grouper
对象,以便您可以浏览每个组的成员.但是如果你没有要求查看小组成员,而只是要求确定下一组,那么groupby
只需要耸耸肩,就像"好吧,你是老板,我就去找下一个小组".
你要做的list(groupby(...))
是要求groupby
识别所有组.所以它就是这样做的.但如果你最后要求每个小组的成员,那groupby
就像"老兄......对不起,我把它们提供给你,但你不想要它们.而且我很懒,所以我不喜欢无缘无故地把事情搞定.我可以给你最后一组的最后一个成员,因为我还记得那一个,但对于那之前的一切......对不起,我只是不再拥有它们了,你应该告诉我你想要他们".
PS在所有这一切中,"懒惰"当然意味着"高效".不是坏事但是好事!
返回的组本身就是一个迭代器,与 共享底层可迭代对象
groupby()
。由于源是共享的,因此当groupby()
对象前进时,前一组不再可见。
将输出转换groupby()
为列表会推进groupby()
对象。
因此,您不应该将对象类型转换itertools.groupby
为列表。如果您想将值存储为list
,那么您应该执行类似列表理解的操作来创建groupby
对象的副本:
grouped_l = [(a, list(b)) for a, b in itertools.groupby(l, key=lambda x:x[0])]
Run Code Online (Sandbox Code Playgroud)
这将允许您多次迭代列表(从对象转换groupby
) 。但是,如果您只想迭代一次结果,那么您在问题中提到的第二个解决方案将满足您的要求。
归档时间: |
|
查看次数: |
514 次 |
最近记录: |