如何使用 itertools.groupby()?
- 2024-12-02 08:41:00
- admin 原创
- 206
问题描述:
我找不到关于如何实际使用 Pythonitertools.groupby()
函数的易懂解释。我想做的是:
列出一个列表 - 在本例中,是对象化
lxml
元素的子元素根据一些标准将其分成几组
然后稍后分别对每个组进行迭代。
我已经查看过文档,但是在尝试将它们应用到简单的数字列表之外时遇到了困难。
那么,我该如何使用itertools.groupby()
?我是否应该使用其他技术?如果能提供指向良好“先决条件”阅读材料的提示,我将不胜感激。
解决方案 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 + ".")
这将给你输出:
动物:熊和鸭子。
植物:仙人掌。
车辆:快艇和校车。
解决方案 2:
itertools.groupby
是一种对项目进行分组的工具。
从文档中,我们进一步了解了它可能的作用:
# [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']
用途
字谜(见笔记本)
分箱
奇数和偶数的分组
按值对列表进行分组
删除重复元素
查找数组中重复元素的索引
将数组拆分成 n 个大小的块
在两个列表之间查找对应元素
压缩算法(参见笔记本)/行程长度编码
按长度、按键功能对字母进行分组(参见笔记本)
超过阈值的连续值(参见笔记本)
查找列表或连续项中的数字范围(参见文档)
查找所有相关的最长序列
采取满足条件的连续序列(参见相关文章)
注意:后面几个例子来自 Víctor Terrón 的 PyCon (talk) (西班牙语),“用 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)]
解决方案 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
方法实际上只是遍历列表,并且每当键发生变化时它就会创建一个新组。
解决方案 4:
groupby 的一个巧妙技巧是在一行中运行长度编码:
[(c,len(list(cgen))) for c,cgen in groupby(some_string)]
将为您提供一个 2 元组列表,其中第一个元素是字符,第二个元素是重复次数。
itertools.groupby
编辑:请注意,这与 SQL语义不同GROUP BY
:itertools 不会(通常不能)提前对迭代器进行排序,因此具有相同“键”的组不会合并。
解决方案 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
。在这种情况下,首先输出一组零,然后输出一组一,然后再次输出一组零。
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]
解决方案 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功能,同时避免那些令人讨厌的(对于小数据)迭代器。
解决方案 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.
有两组携带车辆,而我们预计只有一组
解决方案 8:
遗憾的是,我认为不建议使用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']}
解决方案 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 函数将不起作用!
解决方案 10:
排序和分组
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'}
解决方案 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:
如何使用 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])]
解决方案 13:
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]}
解决方案 14:
我遇到的一个有用的例子可能会有所帮助:
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)
解决方案 15:
要认识到的关键itertools.groupby
是,只有当项目在 iterable 中是连续的时,它们才会分组在一起。这就是排序有效的原因,因为基本上您正在重新排列集合,以便现在满足条件的所有项目都callback(item)
按顺序出现在排序后的集合中。
话虽如此,您不需要对列表进行排序,您只需要一个键值对的集合,其中值可以根据所产生的每个组迭代而增长groupby
。即列表字典。
>>> things = [("vehicle", "bear"), ("animal", "duck"), ("animal", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]
>>> coll = {}
>>> for k, g in itertools.groupby(things, lambda x: x[0]):
... coll.setdefault(k, []).extend(i for _, i in g)
...
{'vehicle': ['bear', 'speed boat', 'school bus'], 'animal': ['duck', 'cactus']}
解决方案 16:
这是一个如何groupby
直接使用字典列表的示例。
from itertools import groupby
items = [{'app_id': '55222702242335', 'mail_id': '4890770'},
{'app_id': '44322702242745', 'mail_id': '4890770'},
{'app_id': '80513948813781', 'mail_id': '5083772'},
{'app_id': '70514248813211', 'mail_id': '5083772'}]
items.sort(key=lambda x: x['mail_id'])
grouped_items = groupby(items, lambda x:x["mail_id"])
result = {}
for key, item in grouped_items:
result[key] = list(item)
print(result)
示例输出
{'4890770': [{'app_id': '55222702242335', 'mail_id': '4890770'}, {'app_id': '44322702242745', 'mail_id': '4890770'}], '5083772': [{'app_id': '80513948813781', 'mail_id': '5083772'}, {'app_id': '70514248813211', 'mail_id': '5083772'}]}
或者使用更 Pythonic 的方式
from itertools import groupby
items = [{'app_id': '55222702242335', 'mail_id': '4890770'},
{'app_id': '44322702242745', 'mail_id': '4890770'},
{'app_id': '80513948813781', 'mail_id': '5083772'},
{'app_id': '70514248813211', 'mail_id': '5083772'}]
items.sort(key=lambda x: x['mail_id'])
result = {key: list(group) for key, group in groupby(items, key=lambda x: x['mail_id'])}
print(result)