【问题标题】:itertools.groupby function seems inconsistentitertools.groupby 函数似乎不一致
【发布时间】:2016-09-12 01:02:38
【问题描述】:

我无法准确理解这个函数的作用,因为我猜是围绕它的使用的编程魔法?

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

例如:

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']]

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

发生了什么事?非常感谢您的洞察力。

【问题讨论】:

  • 只要您推进顶级迭代器,group 迭代器就会失效。

标签: python group-by list-comprehension itertools


【解决方案1】:

文档已经解释了为什么您的 listcomps 不等效:

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

你的

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

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

你的

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

在生成所有组之前不使用任何组。完全不一样,原因是引用的文档解释了。

【讨论】:

    【解决方案2】:

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

    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)] 是一个非常合理的方法。然后,您可以随时在任何子列表中查找数据,而不受有关何时使用迭代器的规则的约束。通常,列表列表比迭代器更容易使用。希望这会有所帮助:-)

    【讨论】:

    • 我希望它这么简单,但似乎抢先将其转换为列表会导致发生一些奇怪的事情,即各个元素的子迭代器变得不准确。见上文,“A”的迭代器在一种情况下转换为空白列表,在另一种情况下完全跳过。
    • 阅读groupby 的文档:docs.python.org/2/library/itertools.html#itertools.groupby 值得注意的是:“返回的组本身就是一个迭代器,它与 groupby() 共享底层迭代。因为源是共享的,当groupby() 对象已高级,前一个组不再可见。”通过抢先转换为列表,您将完全推进外部迭代器,进而推进内部迭代器。
    • @bgenchel:如果您询问[list(thing) for thing in...] 行,请查看您正在迭代的列表:[g for k, g in groupby('AAABBB')]。它包含两个迭代器(_grouper 生成器)——一个实际上是空的,一个几乎是空的。通过文档中的“...大致相当于:”源,每个都准备好yield self.currvalue,但只有最后一个_grouper恰好满足其while循环的self.currkey == tgtkey条件。所以最后一个_grouper 只产生一个值(产生你的['B'] 列表),然后遇到Stopiteration
    • 我认为我对迭代器的了解可能需要一些工作,以便我理解您的答案@KevinJ.Chase。感谢您的详细解释!
    • @Raymond Hettinger 谢谢!
    猜你喜欢
    • 1970-01-01
    • 2020-08-25
    • 2020-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    相关资源
    最近更新 更多