如何使用itertools.groupby()?


506

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

  • 列出一个列表-在这种情况下,是对象化lxml元素的子元素
  • 根据一些标准将其分为几组
  • 然后,稍后分别遍历每个组。

我已经阅读了文档示例,但是尝试将它们应用于简单的数字列表之外时遇到了麻烦。

那么,我该如何使用itertools.groupby()?我应该使用另一种技术吗?指向良好“前提”阅读的指针也将受到赞赏。


Answers:


655

重要说明:您必须先对数据进行排序


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

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

这将为您提供输出:

动物:熊和鸭。
植物:仙人掌。
车辆:快艇和校车。


1
有没有一种方法可以预先指定组,然后不需要排序?
约翰·萨尔瓦捷

2
itertools通常会为我点击,但是对此我也有一个“障碍”。我感谢您的示例-比文档清晰得多。我认为itertools倾向于单击或不单击,如果碰巧遇到类似问题,则易于掌握。尚未在野外使用此工具。
Profane

3
@Julian python文档似乎对大多数东西都很有用,但是当涉及到迭代器,生成器和cherrypy时,这些文档大多使我迷惑。Django的文档令人困惑。
Marc Maxmeister

6
+1进行排序-在对数据进行分组之前,我不明白您的意思。
科迪2014年

4
@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
罗宾·内梅特

71

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


45
所以您读起来keyfunc就像“是的,我确切地知道那是什么,因为该文档非常简单。”?难以置信!
亚拉德(Jarad)

5
我相信大多数人已经知道这个“简单”但无用的示例,因为它没有说明要使用哪种“数据”和“ keyfunc”!但是我想您也不知道,否则您将通过澄清它而不只是复制粘贴来帮助人们。还是你
Apostolos

69

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

用途

注意:后面的几个示例来自VíctorTerrón的PyCon (谈话) (西班牙语),“黎明时的功夫与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)]

1
从技术上讲,文档可能应该说[''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
Mateen Ulhaq

1
是。大多数itertools文档字符串都是以这种方式“删节”的。由于所有迭代工具的是迭代器,它们必须被转换为内置(list()tuple())或消耗在一个循环/理解显示的内容。这些冗余可能被作者排除在外以节省空间。
pylang '18 -10-25

39

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

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

将为您提供2元组的列表,其中第一个元素是char,第二个是重复数。

编辑:请注意,这是itertools.groupby与SQL GROUP BY语义分离的内容:itertools不会(通常不能)事先对迭代器进行排序,因此不会合并具有相同“键”的组。


27

另一个例子:

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]

这很有趣,但是itertools.islice可以更好地分块可迭代对象吗?它返回一个像生成器一样进行迭代的对象,但是它使用C代码。
trojjer 2013年

如果组大小一致,则@trojjer islice会更好。
woodm1979

我想得到:[0,1,2],[1,2,3],[2,3,4] ...
GilbertS

21

警告:

语法列表(groupby(...))不能按您期望的方式工作。似乎破坏了内部迭代器对象,因此使用

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

将产生:

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

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

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

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


3
许多答案都涉及到绊脚石,您必须在绊脚石之前对绊脚石进行排序才能获得预期的结果。我刚刚遇到了这个答案,这说明了我之前从未见过的奇怪行为。我以前从没见过,因为直到现在我才试图像@singular所说的那样列出(groupby(range(10))。让list()构造函数“自动”做到这一点
红豆2014年

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.

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


5
您必须首先对数据进行排序,并使用要分组的功能作为键。上面的两个帖子中提到了这一点,但未突出显示。
mbatchkarov 2013年

我一直在进行dict理解,以按键保留子迭代器,直到意识到这就像dict(groupby(iterator,key))一样简单。甜。
trojjer 2013年

经过深思熟虑,经过实验,包裹在groupby周围的dict调用将耗尽组子迭代器。该死的。
trojjer 2013年

这个答案有什么意义?它是如何建立在原始答案上的
codeforester

7

@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函数将无法工作


7
确实有效。您可能会认为此行为已损坏,但在某些情况下很有用。见这个问题的答案为例:stackoverflow.com/questions/1553275/...
丹尼斯Otkidach

6

排序和分组

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

5

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

1

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

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)


1

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

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

0

您可以编写自己的groupby函数:

           def groupby(data):
                kv = {}
                for k,v in data:
                    if k not in kv:
                         kv[k]=[v]
                    else:
                        kv[k].append(v)
           return kv

     Run on ipython:
       In [10]: data = [('a', 1), ('b',2),('a',2)]

        In [11]: groupby(data)
        Out[11]: {'a': [1, 2], 'b': [2]}

1
重塑轮子不是一个好主意,问题还在于解释itertools groupby,而不是自己编写
user2678074

1
@ user2678074你是对的。如果您想为学习的观点而写自己的东西,那是一件好事。
天空

2
最好也使用defaultdict(list),以便它更短
Mickey Perlstein

@MickeyPerlstein和更快。
funnydman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.