【问题标题】:How do I use itertools.groupby()?如何使用 itertools.groupby()?
【发布时间】:2010-09-05 06:32:30
【问题描述】:

对于如何实际使用 Python 的 itertools.groupby() 函数,我无法找到可以理解的解释。我想要做的是:

  • 获取一个列表 - 在这种情况下,对象化 lxml 元素的子元素
  • 根据某些标准将其分组
  • 然后分别迭代这些组中的每一个。

我已经查看了the documentation,但尝试将它们应用于简单的数字列表时遇到了麻烦。

那么,我该如何使用itertools.groupby()?我应该使用另一种技术吗?指向良好的“先决条件”阅读的指针也将不胜感激。

【问题讨论】:

标签: python itertools


【解决方案1】:

重要提示:您必须先对数据进行排序


我没有得到的部分是在示例构造中

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
   groups.append(list(g))    # Store group iterator as a list
   uniquekeys.append(k)

k 是当前分组键,g 是一个迭代器,可用于迭代由该分组键定义的组。换句话说,groupby 迭代器本身返回迭代器。

这是一个例子,使用更清晰的变量名:

from itertools import groupby

things = [("animal", "bear"), ("animal", "duck"), ("plant", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print("A %s is a %s." % (thing[1], key))
    print("")
    

这将为您提供输出:

熊是一种动物。
鸭子是动物。

仙人掌是一种植物。

快艇是一种交通工具。
校车是一种交通工具。

在本例中,things 是一个元组列表,其中每个元组中的第一项是第二项所属的组。

groupby() 函数有两个参数:(1) 要分组的数据和 (2) 要分组的函数。

这里,lambda x: x[0] 告诉groupby() 使用每个元组中的第一项作为分组键。

在上面的for 语句中,groupby 返回三个(键,组迭代器)对 - 每个唯一键一次。您可以使用返回的迭代器来迭代该组中的每个单独项目。

这是一个稍微不同的示例,使用列表推导,使用相同的数据:

for key, group in groupby(things, lambda x: x[0]):
    listOfThings = " and ".join([thing[1] for thing in group])
    print(key + "s:  " + listOfThings + ".")

这将为您提供输出:

动物:熊和鸭子。
植物:仙人掌。
交通工具:快艇和校车。

【讨论】:

  • 有没有办法预先指定组,然后不需要排序?
  • itertools 通常会为我点击,但我也有一个“块”。我很欣赏你的例子——比文档清楚得多。我认为 itertools 倾向于点击或不点击,如果你碰巧遇到类似的问题,它更容易掌握。在野外还不需要这个。
  • @Julian python 文档对于大多数东西来说似乎都很棒,但是当涉及到迭代器、生成器和cherrypy时,这些文档大多让我感到困惑。 Django 的文档令人费解。
  • +1 用于排序 -- 直到我对数据进行分组,我才明白你的意思。
  • @DavidCrook 参加聚会很晚,但可能会帮助某人。这可能是因为您的数组未排序尝试groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0])) 假设my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")] 并且您想按animal or plant 分组
【解决方案2】:

itertools.groupby 是一个分组项目的工具。

来自the docs,我们进一步收集了它可能会做什么:

# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B

# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

groupby 对象产生密钥组对,其中组是生成器。

特点

  • A.将连续的项目组合在一起
  • 乙。给定一个排序的可迭代项,对所有出现的项目进行分组
  • C.指定如何使用 键功能 * 对项目进行分组

比较

# Define a printer for comparing outputs
>>> def print_groupby(iterable, keyfunc=None):
...    for k, g in it.groupby(iterable, keyfunc):
...        print("key: '{}'--> group: {}".format(k, list(g)))
# Feature A: group consecutive occurrences
>>> print_groupby("BCAACACAADBBB")
key: 'B'--> group: ['B']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'D'--> group: ['D']
key: 'B'--> group: ['B', 'B', 'B']

# Feature B: group all occurrences
>>> print_groupby(sorted("BCAACACAADBBB"))
key: 'A'--> group: ['A', 'A', 'A', 'A', 'A']
key: 'B'--> group: ['B', 'B', 'B', 'B']
key: 'C'--> group: ['C', 'C', 'C']
key: 'D'--> group: ['D']

# Feature C: group by a key function
>>> # islower = lambda s: s.islower()                      # equivalent
>>> def islower(s):
...     """Return True if a string is lowercase, else False."""   
...     return s.islower()
>>> print_groupby(sorted("bCAaCacAADBbB"), keyfunc=islower)
key: 'False'--> group: ['A', 'A', 'A', 'B', 'B', 'C', 'C', 'D']
key: 'True'--> group: ['a', 'a', 'b', 'b', 'c']

用途

注意:后面的几个例子来自 Víctor Terrón 的 PyCon (talk) (Spanish),“Kung Fu at Dawn with Itertools”。另请参阅用 C 编写的groupby source code

* 传递和比较所有项目的函数,影响结果。其他具有关键功能的对象包括sorted()max()min()


回应

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]

【讨论】:

  • 从技术上讲,文档应该说[''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
  • 是的。大多数 itertools 文档字符串都是以这种方式“删节”的。由于所有的 itertools 都是迭代器,它们必须被强制转换为内置函数(list()tuple())或在循环/理解中使用以显示内容。这些是作者可能为了节省空间而排除的冗余。
【解决方案3】:

Python 文档中的示例非常简单:

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

因此,在您的情况下,数据是一个节点列表,keyfunc 是您的标准函数的逻辑所在,然后groupby() 对数据进行分组。

在调用groupby 之前,您必须小心按条件对数据进行排序,否则它将不起作用。 groupby 方法实际上只是遍历一个列表,每当键更改时,它都会创建一个新组。

【讨论】:

  • 所以你读了keyfunc 并且就像“是的,我知道那是什么,因为这个文档非常简单。”?难以置信!
  • 我相信大多数人已经知道这个“直截了当”但没用的例子,因为它没有说明使用什么样的“数据”和“键函数”!但我想你也不知道,否则你会通过澄清它来帮助人们,而不仅仅是复制粘贴它。还是你?
  • 我会说,虽然只是在文档中粘贴已经引用的问题绝不是一个有用的答案,但下面的附加声明是一个很好的提醒。数据必须首先按 keyfunc 排序。因此,如果用户有一个类列表并且她希望按 obj.attr_a、grouping_target = sorted(obj_list, key=lambda o: o.attr_a)groups = itertools.groupby(grouping_target, key=lambda o: o.attr_a) 进行分组。否则,如前所述,它将不起作用,您会看到 groupby 键重复。
【解决方案4】:

groupby 的一个技巧是在一行中运行长度编码:

[(c,len(list(cgen))) for c,cgen in groupby(some_string)]

会给你一个 2 元组列表,其中第一个元素是 char,第二个是重复次数。

编辑:请注意,itertools.groupby 与 SQL GROUP BY 语义的区别在于:itertools 不会(通常也不能)提前对迭代器进行排序,因此具有相同“键”的组不会t 合并。

【讨论】:

    【解决方案5】:

    另一个例子:

    for key, igroup in itertools.groupby(xrange(12), lambda x: x // 5):
        print key, list(igroup)
    

    结果

    0 [0, 1, 2, 3, 4]
    1 [5, 6, 7, 8, 9]
    2 [10, 11]
    

    请注意,igroup 是一个迭代器(文档称之为子迭代器)。

    这对于分块生成器很有用:

    def chunker(items, chunk_size):
        '''Group items in chunks of chunk_size'''
        for _key, group in itertools.groupby(enumerate(items), lambda x: x[0] // chunk_size):
            yield (g[1] for g in group)
    
    with open('file.txt') as fobj:
        for chunk in chunker(fobj):
            process(chunk)
    

    groupby 的另一个示例 - 当键未排序时。在以下示例中,xx 中的项目按yy 中的值分组。在这种情况下,首先输出一组零,然后输出一组 1,然后再输出一组零。

    xx = range(10)
    yy = [0, 0, 0, 1, 1, 1, 0, 0, 0, 0]
    for group in itertools.groupby(iter(xx), lambda x: yy[x]):
        print group[0], list(group[1])
    

    生产:

    0 [0, 1, 2]
    1 [3, 4, 5]
    0 [6, 7, 8, 9]
    

    【讨论】:

    • 这很有趣,但 itertools.islice 对可迭代对象进行分块不是更好吗?它返回一个像生成器一样迭代的对象,但它使用 C 代码。
    • 如果组大小一致,@trojjer islice 会更好。
    【解决方案6】:

    警告:

    语法 list(groupby(...)) 不会按您想要的方式工作。它似乎破坏了内部迭代器对象,所以使用

    for x in list(groupby(range(10))):
        print(list(x[1]))
    

    将产生:

    []
    []
    []
    []
    []
    []
    []
    []
    []
    [9]
    

    代替 list(groupby(...)),尝试 [(k, list(g)) for k,g in groupby(...)],或者如果您经常使用该语法,

    def groupbylist(*args, **kwargs):
        return [(k, list(g)) for k, g in groupby(*args, **kwargs)]
    

    并访问 groupby 功能,同时避免那些讨厌的(对于小数据)迭代器。

    【讨论】:

    • 很多答案都提到了你必须在 groupby 之前排序才能获得预期结果的绊脚石。我刚刚遇到了这个答案,它解释了我以前从未见过的奇怪行为。我以前没见过,因为直到现在我才尝试 list(groupby(range(10)) 正如@singular 所说。在此之前,我一直使用“手动”迭代 groupby 对象的“推荐”方法,而不是让 list() 构造函数“自动”完成。
    【解决方案7】:

    我想举另一个例子,没有排序的 groupby 不起作用。改编自 James Sulak 的示例

    from itertools import groupby
    
    things = [("vehicle", "bear"), ("animal", "duck"), ("animal", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]
    
    for key, group in groupby(things, lambda x: x[0]):
        for thing in group:
            print "A %s is a %s." % (thing[1], key)
        print " "
    

    输出是

    A bear is a vehicle.
    
    A duck is a animal.
    A cactus is a animal.
    
    A speed boat is a vehicle.
    A school bus is a vehicle.
    

    有两组有车辆,而一个人只能指望一组

    【讨论】:

    • 您必须首先对数据进行排序,使用您分组的功能作为键。这在上面的两篇文章中提到过,但没有突出显示。
    • 我正在做一个 dict 理解来按键保存子迭代器,直到我意识到这就像 dict(groupby(iterator, key)) 一样简单。甜蜜。
    • 重新考虑和实验后,围绕 groupby 的 dict 调用将耗尽组子迭代器。该死的。
    • 这个答案有什么意义?它是如何在original answer 上构建的?
    【解决方案8】:

    @CaptSolo,我尝试了您的示例,但没有成功。

    from itertools import groupby 
    [(c,len(list(cs))) for c,cs in groupby('Pedro Manoel')]
    

    输出:

    [('P', 1), ('e', 1), ('d', 1), ('r', 1), ('o', 1), (' ', 1), ('M', 1), ('a', 1), ('n', 1), ('o', 1), ('e', 1), ('l', 1)]
    

    如您所见,有两个 o 和两个 e,但它们分为不同的组。那时我意识到您需要对传递给 groupby 函数的列表进行排序。所以,正确的用法是:

    name = list('Pedro Manoel')
    name.sort()
    [(c,len(list(cs))) for c,cs in groupby(name)]
    

    输出:

    [(' ', 1), ('M', 1), ('P', 1), ('a', 1), ('d', 1), ('e', 2), ('l', 1), ('n', 1), ('o', 2), ('r', 1)]
    

    记住,如果列表没有排序,groupby函数不起作用

    【讨论】:

    【解决方案9】:

    排序和分组

    from itertools import groupby
    
    val = [{'name': 'satyajit', 'address': 'btm', 'pin': 560076}, 
           {'name': 'Mukul', 'address': 'Silk board', 'pin': 560078},
           {'name': 'Preetam', 'address': 'btm', 'pin': 560076}]
    
    
    for pin, list_data in groupby(sorted(val, key=lambda k: k['pin']),lambda x: x['pin']):
    ...     print pin
    ...     for rec in list_data:
    ...             print rec
    ... 
    o/p:
    
    560076
    {'name': 'satyajit', 'pin': 560076, 'address': 'btm'}
    {'name': 'Preetam', 'pin': 560076, 'address': 'btm'}
    560078
    {'name': 'Mukul', 'pin': 560078, 'address': 'Silk board'}
    

    【讨论】:

      【解决方案10】:

      如何使用 Python 的 itertools.groupby()?

      您可以使用 groupby 对要迭代的事物进行分组。你给 groupby 一个 iterable 和一个可选的 key 函数/可调用,通过它来检查从 iterable 出来的项目,它返回一个迭代器,它给出了结果的二元组键可调用和另一个迭代中的实际项目。来自帮助:

      groupby(iterable[, keyfunc]) -> create an iterator which returns
      (key, sub-iterator) grouped by each value of key(value).
      

      这是一个 groupby 示例,它使用协程按计数进行分组,它使用一个可调用的键(在本例中为coroutine.send)来为多次迭代和一个分组的元素子迭代器吐出计数:

      import itertools
      
      
      def grouper(iterable, n):
          def coroutine(n):
              yield # queue up coroutine
              for i in itertools.count():
                  for j in range(n):
                      yield i
          groups = coroutine(n)
          next(groups) # queue up coroutine
      
          for c, objs in itertools.groupby(iterable, groups.send):
              yield c, list(objs)
          # or instead of materializing a list of objs, just:
          # return itertools.groupby(iterable, groups.send)
      
      list(grouper(range(10), 3))
      

      打印

      [(0, [0, 1, 2]), (1, [3, 4, 5]), (2, [6, 7, 8]), (3, [9])]
      

      【讨论】:

        【解决方案11】:

        这个基本实现帮助我理解了这个功能。希望它也对其他人有所帮助:

        arr = [(1, "A"), (1, "B"), (1, "C"), (2, "D"), (2, "E"), (3, "F")]
        
        for k,g in groupby(arr, lambda x: x[0]):
            print("--", k, "--")
            for tup in g:
                print(tup[1])  # tup[0] == k
        
        -- 1 --
        A
        B
        C
        -- 2 --
        D
        E
        -- 3 --
        F
        

        【讨论】:

          【解决方案12】:

          我遇到的一个有用的例子可能会有所帮助:

          from itertools import groupby
          
          #user input
          
          myinput = input()
          
          #creating empty list to store output
          
          myoutput = []
          
          for k,g in groupby(myinput):
          
              myoutput.append((len(list(g)),int(k)))
          
          print(*myoutput)
          

          样本输入:14445221

          样本输出:(1,1) (3,4) (1,5) (2,2) (1,1)

          【讨论】:

            【解决方案13】:

            遗憾的是,我认为不建议使用 itertools.groupby()。安全使用太难了,写几行就可以按预期工作。

            def my_group_by(iterable, keyfunc):
                """Because itertools.groupby is tricky to use
            
                The stdlib method requires sorting in advance, and returns iterators not
                lists, and those iterators get consumed as you try to use them, throwing
                everything off if you try to look at something more than once.
                """
                ret = defaultdict(list)
                for k in iterable:
                    ret[keyfunc(k)].append(k)
                return dict(ret)
            

            像这样使用它:

            def first_letter(x):
                return x[0]
            
            my_group_by('four score and seven years ago'.split(), first_letter)
            

            得到

            {'f': ['four'], 's': ['score', 'seven'], 'a': ['and', 'ago'], 'y': ['years']}
            

            【讨论】:

            • 您能否详细说明为什么它很难安全使用?
            • @ctholho 它在文档字符串中进行了解释,如果有人查看代码并想知道为什么它不使用标准库方法,它将很容易获得:“stdlib 方法需要提前排序,并且返回迭代器而不是列表,当你尝试使用它们时,这些迭代器会被消耗掉,如果你尝试多次查看某个东西,就会把所有东西都扔掉。”
            【解决方案14】:
            from random import randint
            from itertools import groupby
            
             l = [randint(1, 3) for _ in range(20)]
            
             d = {}
             for k, g in groupby(l, lambda x: x):
                 if not d.get(k, None):
                     d[k] = list(g)
                 else:
                     d[k] = d[k] + list(g)
            

            上面的代码显示了如何使用 groupby 根据提供的 lambda 函数/键对列表进行分组。唯一的问题是输出没有合并,这可以使用字典轻松解决。

            例子:

            l = [2, 1, 2, 3, 1, 3, 2, 1, 3, 3, 1, 3, 2, 3, 1, 2, 1, 3, 2, 3]
            

            应用 groupby 后,结果将是:

            for k, g in groupby(l, lambda x:x):
                print(k, list(g))
            
            2 [2]
            1 [1]
            2 [2]
            3 [3]
            1 [1]
            3 [3]
            2 [2]
            1 [1]
            3 [3, 3]
            1 [1]
            3 [3]
            2 [2]
            3 [3]
            1 [1]
            2 [2]
            1 [1]
            3 [3]
            2 [2]
            3 [3]
            

            一旦使用了如上所示的字典,就会得出以下结果,可以轻松地对其进行迭代:

            {2: [2, 2, 2, 2, 2, 2], 1: [1, 1, 1, 1, 1, 1], 3: [3, 3, 3, 3, 3, 3, 3, 3]}
            

            【讨论】:

            • 请解释这段代码是如何回答问题的(实际上是在询问如何使用groupby)。另外,代码有缩进错误。
            猜你喜欢
            • 2017-10-05
            • 2016-08-02
            • 1970-01-01
            • 2019-09-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-12-03
            相关资源
            最近更新 更多