groupby周围的列表导致空组

时间:2018-01-27 12:38:15

标签: python iterator grouping itertools

我正在四处玩,以便对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')]]

3 个答案:

答案 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对象转换)。但是,如果您只对迭代结果一次感兴趣,那么您在问题中提到的第二个解决方案就足以满足您的要求。