关于迭代:如何使用python的itertools.groupby()?

How do I use Python's itertools.groupby()?

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

  • 取一个列表-在本例中,对象化lxml元素的子元素
  • 根据某些标准将其分成若干组
  • 然后分别对这些组中的每个组进行迭代。

我已经审阅了文档和示例,但在尝试将它们应用于简单的数字列表之外时遇到了困难。

那么,如何使用itertools.groupby()?我还应该使用其他技术吗?同时,也会感谢对良好"先决条件"阅读的提示。


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

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

1
2
3
4
5
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迭代器本身返回迭代器。

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

1
2
3
4
5
6
7
8
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""

这将为您提供输出:

A bear is a animal.
A duck is a animal.

A cactus is a plant.

A speed boat is a vehicle.
A school bus is a vehicle.

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

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

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

在上面的for语句中,groupby返回三对(key、group iterator)对-每个唯一的key一次。可以使用返回的迭代器对该组中的每个项进行迭代。

下面是一个使用相同数据的稍微不同的示例,使用列表理解:

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

这将为您提供输出:

animals: bear and duck.
plants: cactus.
vehicles: speed boat and school bus.


你能给我们看看你的密码吗?

关于python文档的示例非常简单:

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

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

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


Neato与Groupby的一个诀窍是在一行中运行长度编码:

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

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

编辑:请注意,这是将itertools.groupby与SQL GROUP BY语义分开的原因:itertools不会(通常不能)提前对迭代器排序,因此具有相同"key"的组不会合并。


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.对一个项目的所有出现项进行分组,给出一个已排序的iterable
  • c.指定如何使用键函数对项进行分组

比较

1
2
3
4
# Define a printer for comparing outputs
>>> def print_groupby(iterable, key=None):
...    for k, g in it.groupby(iterable, key):
...        print("key: '{}'--> group: {}".format(k, list(g)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 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
>>> key = lambda x: x.islower()
>>> print_groupby(sorted("bCAaCacAADBbB"), key)
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源代码。

响应

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


另一个例子:

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

结果在

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

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

这有助于对发电机进行分块:

1
2
3
4
5
6
7
8
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
2
3
4
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])

生产:

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


警告:

语法列表(groupby(…)将无法按预期方式工作。它似乎会破坏内部迭代器对象,因此使用

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

将产生:

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

相反,在groupby(…)]中尝试[(k,list(g))for k,g for k,或者如果经常使用该语法,

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

并且可以访问groupby功能,同时避免这些麻烦的(对于小数据)迭代器。


我想再举一个没有排序的GroupBy不起作用的例子。改编自詹姆斯·苏拉克的例子

1
2
3
4
5
6
7
8
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""

输出是

1
2
3
4
5
6
7
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.

有两组有车辆,而一组只有一组


@Captsolo,我试过你的例子,但没用。

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

输出:

1
[('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函数的列表进行排序。因此,正确的用法是:

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

输出:

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

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


Sorting and groupby

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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'}


How do I use Python's itertools.groupby()?

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

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

下面是group by的一个例子,它使用coroutine按计数分组,它使用一个键可调用(在本例中是coroutine.send)来为多少次迭代和一个分组的元素子迭代器吐出计数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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))

印刷品

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
           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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)