614

我一直无法找到关于如何实际使用 Pythonitertools.groupby()函数的可以理解的解释。我想要做的是:

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

我已经查看了文档,但是在尝试将它们应用到简单的数字列表之外时遇到了麻烦。

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

4

14 回答 14

783

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


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

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 + ".")

这将为您提供输出:

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

于 2008-08-10T18:45:32.430 回答
134

itertools.groupby是用于对项目进行分组的工具。

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. 将连续的项目组合在一起
  • B. 给定一个排序的可迭代项,对所有出现的项目进行分组
  • 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 (谈话) (西班牙语),“Kung Fu at Dawn with Itertools”。另请参阅用 C 编写的groupby 源代码。

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


回复

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
于 2017-08-25T02:26:21.040 回答
73

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方法实际上只是遍历一个列表,每当键更改时,它都会创建一个新组。

于 2008-08-03T18:40:09.053 回答
50

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

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

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

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

于 2008-08-31T23:27:16.920 回答
33

另一个例子:

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]
于 2013-01-21T16:54:08.263 回答
24

警告:

语法 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 功能,同时避免那些讨厌的(对于小数据)迭代器。

于 2013-11-16T00:39:31.500 回答
12

我想举另一个例子,没有排序的 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.

有两组有车辆,而一个可以预期只有一组

于 2013-05-07T20:09:46.677 回答
9

@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 功能将不起作用

于 2009-10-15T15:41:51.620 回答
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'}
于 2017-08-01T07:14:01.103 回答
7

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

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

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])]
于 2015-07-27T18:06:30.897 回答
5

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

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
于 2020-04-05T19:46:07.443 回答
5

可悲的是,我认为不建议使用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']}
于 2021-06-22T22:59:17.283 回答
4

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

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)

于 2017-06-18T17:16:54.393 回答
0
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]}
于 2021-10-31T04:03:02.753 回答