itertools.groupby函数似乎不一致

时间:2016-09-12 01:02:38

标签: python group-by list-comprehension itertools

我无法准确理解这个功能究竟是什么,因为我猜,它的编程魔术围绕它使用?

在我看来它似乎返回一个键的列表(字符串中的唯一字母)与迭代器配对,它引用原始字符串中每个字母的数量列表,但有时似乎这不是案件。

例如:

import itertools

x = list(itertools.groupby("AAABBB"))
print x

打印:

[('A', <itertools._grouper object at 0x101a0b050), 
 ('B', <itertools._grouper object at 0x101a0b090)]

这似乎是正确的,我们将我们的唯一键与迭代器配对。但是当我跑步时:

print list(x[0][1])

我明白了:

[]

当我跑

for k, g in x:
    print k + ' - ' + g

我明白了:

B - <itertools._grouper object at 0x1007eedd5>

它忽略了第一个元素。这似乎是违反直觉的,因为如果我只是稍微改变一下语法:

[list(g) for k, g in itertools.groupby("AAABBB")]

我明白了:

[["A", "A", "A"], ["B", "B", "B"]]

这是正确的,并且与我认为该功能应该做的事情保持一致。

但是,如果我再一次改变语法:

[list(thing) for thing in [g for k, g in itertools.groupby(string)]]

我回来了:

[[], ['B']]

这两个列表推导应该是直接等价的,但它们会返回不同的结果。

发生了什么事?洞察力将非常感激。

2 个答案:

答案 0 :(得分:6)

文档已经解释了为什么listcomps不等同:

  

返回的组本身就是一个迭代器,它与groupby()共享底层的iterable。由于源是共享的,因此当groupby()对象处于高级时,前一个组将不再可见。因此,如果以后需要该数据,则应将其存储为列表

[list(g) for k, g in itertools.groupby("AAABBB")]

确实groupby()前进之前使用每个组,因此它可以正常工作。

[list(thing) for thing in [g for k, g in itertools.groupby(string)]]
在生成所有组之后,

才会使用任何组。完全不一样,并且引用文档解释的原因。

答案 1 :(得分:4)

要获得您期望的答案,请将返回的迭代器转换为列表。

Groupby懒惰地使用输入迭代器(这意味着它只在需要时读取数据)。要查找新组,需要读取下一个不相等的元素(下一组的第一个成员)。如果您列出子组迭代器,它会将输入推进到当前组的末尾。

通常,如果您前进到下一个组,则先前返回的子组迭代器将没有数据,并且将显示为空。因此,如果您需要子组迭代器中的数据,则需要列出 之前前进到下一组。

这种行为的原因是迭代器都是关于一次查看一个数据并且不在内存中保留任何不必要的内容。

以下是一些使所有操作可见的代码:

from itertools import groupby

def supply():
    'Make the lazy input visible'
    for c in 'aaaaabbbcdddddddeeee':
        print('supplying %r' % c)
        yield c

print("\nCase where we don't consume the sub-iterator")
for k, g in groupby(supply()):
    print('Got group for %r' % k)

print("\nCase where we do consume the sub-iterator before advancing")
for k, g in groupby(supply()):
    print('Got group for %r' % k)
    print(list(g))

在“让你疯狂”的例子中, list 操作的应用太晚了(在外部列表理解中)。解决方案是将 list 步骤移至内部理解:

>>> import itertools
>>> [list(g) for k, g in itertools.groupby('aaaaabbbb')]
>>> [['a', 'a', 'a', 'a', 'a'], ['b', 'b', 'b', 'b']]

如果你真的不关心节省内存,那么运行grouped = [list(g) for k, g in itertools.groupby(data)]是一个非常合理的方法。然后,您可以随时在任何子列表中查找数据,而不受有关何时使用迭代器的规则的约束。通常,列表列表比迭代器更容易使用。希望这会有所帮助: - )