如何使用itertools.groupby()?

我还没有找到一个可以理解的解释如何实际使用Python的itertools.groupby()函数。我想做的是:

  • 取一个列表——在本例中为物化的lxml元素的子元素
  • 根据某些标准将其分成几组
  • 然后分别遍历这些组。

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

那么,我如何使用itertools.groupby()?还有其他我应该使用的技巧吗?良好“先决条件”的指针;也欢迎阅读。

412157 次浏览

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

你必须先对数据排序


我没有理解的部分是在例子结构中

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

这将给你输出:

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

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

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

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

编辑:注意,这是区分itertools.groupby和SQL GROUP BY语义的地方:itertools不会(通常也不能)提前对迭代器排序,因此具有相同“key”的组不会合并。

@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函数不会起作用!

另一个例子:

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中的值分组。在这种情况下,首先输出一组0,然后是一组1,然后又是一组0。

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]

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

有两组有车辆,而我们只能期待一组

警告:

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

我如何使用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])]

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

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)

排序和分组

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

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

的文档,我们进一步收集它可能做什么:

# EYZ0

# EYZ0

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 (talk) (西班牙语), "Kung Fu at Dawn with Itertools"另见groupby 源代码用C编写。

*一个函数,其中所有项都被传递和比较,影响结果。其他具有键函数的对象包括sorted()max()min()


响应

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

这个基本实现帮助我理解了这个函数。希望它也能帮助到其他人:

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

遗憾的是,我不认为使用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']}
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]}