Làm cách nào để sử dụng itertools.groupby ()?


507

Tôi chưa thể tìm thấy một lời giải thích dễ hiểu về cách thực sự sử dụng itertools.groupby()chức năng của Python . Những gì tôi đang cố gắng làm là:

  • Lấy một danh sách - trong trường hợp này, con của một lxmlphần tử được đối tượng hóa
  • Chia nó thành các nhóm dựa trên một số tiêu chí
  • Sau đó lặp lại qua từng nhóm riêng biệt.

Tôi đã xem lại tài liệucác ví dụ , nhưng tôi gặp khó khăn khi cố gắng áp dụng chúng ngoài một danh sách các số đơn giản.

Vì vậy, làm thế nào để tôi sử dụng itertools.groupby()? Có một kỹ thuật khác tôi nên sử dụng? Con trỏ đến việc đọc "điều kiện tiên quyết" tốt cũng sẽ được đánh giá cao.


một trường hợp hữu ích cho việc đó sẽ là leetcode.com/probols/opes-compression
ShawnLee

Câu trả lời:


656

LƯU Ý QUAN TRỌNG: Bạn phải sắp xếp dữ liệu của bạn trước.


Phần tôi không nhận được là trong ví dụ xây dựng

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

klà khóa nhóm hiện tại và glà một trình vòng lặp mà bạn có thể sử dụng để lặp qua nhóm được xác định bởi khóa nhóm đó. Nói cách khác, groupbychính trình lặp đó trả về các trình vòng lặp.

Đây là một ví dụ về điều đó, sử dụng tên biến rõ ràng hơn:

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

Điều này sẽ cung cấp cho bạn đầu ra:

Một con gấu là một con vật.
Một con vịt là một con vật.

Một cây xương rồng là một loại cây.

Một chiếc thuyền tốc độ là một phương tiện.
Xe buýt trường học là một phương tiện.

Trong ví dụ này, thingslà một danh sách các bộ dữ liệu trong đó mục đầu tiên trong mỗi bộ là nhóm mục thứ hai thuộc về.

Các groupby()chức năng hai đối số: (1) các dữ liệu để nhóm và (2) các chức năng để nhóm nó với.

Ở đây, lambda x: x[0]nói groupby()để sử dụng mục đầu tiên trong mỗi bộ dữ liệu làm khóa nhóm.

Trong forcâu lệnh trên , groupbytrả về ba cặp (khóa, vòng lặp nhóm) - một lần cho mỗi khóa duy nhất. Bạn có thể sử dụng trình lặp được trả về để lặp lại qua từng mục riêng lẻ trong nhóm đó.

Đây là một ví dụ hơi khác với cùng một dữ liệu, sử dụng cách hiểu danh sách:

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

Điều này sẽ cung cấp cho bạn đầu ra:

động vật: gấu và vịt.
cây: xương rồng.
phương tiện: tàu cao tốc và xe buýt trường học.


1
Có cách nào để xác định các nhóm trước và sau đó không yêu cầu sắp xếp?
John Salvatier

2
itertools thường nhấp cho tôi, nhưng tôi cũng có một 'khối' cho cái này. Tôi đánh giá cao các ví dụ của bạn - rõ ràng hơn nhiều so với tài liệu. Tôi nghĩ rằng itertools có xu hướng nhấp hoặc không, và dễ nắm bắt hơn nhiều nếu bạn gặp phải vấn đề tương tự. Chưa cần cái này trong tự nhiên.
Profane

3
Tài liệu python @Julian có vẻ tuyệt vời cho hầu hết mọi thứ nhưng khi nói đến iterators, máy phát điện và cherrypy, các tài liệu chủ yếu làm tôi bối rối. Các tài liệu của Django đang gặp khó khăn gấp đôi.
Marc Maxmeister

6
+1 cho việc sắp xếp - Tôi không hiểu ý của bạn cho đến khi tôi nhóm dữ liệu của mình.
Cody

4
@DavidCrook đến bữa tiệc rất muộn nhưng có thể giúp được ai đó. Có thể là do mảng của bạn không được sắp xếp thử groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))theo giả định đó my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]và bạn muốn nhóm theoanimal or plant
Robin Nemeth

72

Ví dụ về các tài liệu Python khá đơn giản:

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

Vì vậy, trong trường hợp của bạn, dữ liệu là một danh sách các nút, keyfunclà nơi logic của hàm tiêu chí của bạn đi và sau đó groupby()nhóm dữ liệu.

Bạn phải cẩn thận sắp xếp dữ liệu theo tiêu chí trước khi bạn gọi groupbyhoặc nó sẽ không hoạt động. groupbyphương thức thực sự chỉ lặp qua một danh sách và bất cứ khi nào khóa thay đổi, nó sẽ tạo ra một nhóm mới.


46
Vì vậy, bạn đã đọc keyfuncvà giống như "vâng, tôi biết chính xác đó là gì vì tài liệu này khá đơn giản."? Đáng kinh ngạc!
Jarad

5
Tôi tin rằng hầu hết mọi người đã biết về ví dụ "đơn giản" nhưng vô dụng này, vì nó không nói loại 'dữ liệu' và 'keyfunc' nào để sử dụng !! Nhưng tôi đoán bạn cũng không biết, nếu không bạn sẽ giúp mọi người bằng cách làm rõ nó và không chỉ sao chép nó. Hay bạn
Tông đồ

69

itertools.groupby là một công cụ để nhóm các mục.

Từ các tài liệu , chúng tôi lượm lặt thêm những gì nó có thể làm:

# [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 các đối tượng mang lại các cặp nhóm khóa trong đó nhóm là một trình tạo.

Đặc trưng

  • A. Nhóm các mục liên tiếp với nhau
  • B. Nhóm tất cả các lần xuất hiện của một mục, được lặp lại sắp xếp
  • C. Chỉ định cách nhóm các mục với chức năng chính *

So sánh

# 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']

Công dụng

Lưu ý: Một số ví dụ sau xuất phát từ Víctor Terrón's PyCon (thảo luận) (tiếng Tây Ban Nha) , "Kung Fu lúc bình minh với Itertools". Xem thêm groupbymã nguồn viết bằng C.

* Một chức năng trong đó tất cả các mục được chuyển qua và so sánh, ảnh hưởng đến kết quả. Các đối tượng khác có chức năng chính bao gồm sorted(), max()min().


Phản ứng

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

1
Về mặt kỹ thuật, các tài liệu có lẽ nên nói [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq

1
Đúng. Hầu hết các tài liệu itertools được "rút ngắn" theo cách này. Vì tất cả các itertools đều là các trình vòng lặp, chúng phải được chuyển thành một nội trang ( list(), tuple()) hoặc được sử dụng trong một vòng lặp / hiểu để hiển thị nội dung. Đây là những dư thừa mà tác giả có khả năng loại trừ để bảo tồn không gian.
pylang

39

Một mẹo gọn gàng với nhóm là chạy mã hóa độ dài trong một dòng:

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

sẽ cung cấp cho bạn một danh sách gồm 2 bộ dữ liệu trong đó phần tử đầu tiên là char và thứ 2 là số lần lặp lại.

Chỉnh sửa: Lưu ý rằng đây là những gì tách biệt itertools.groupbyvới GROUP BYngữ nghĩa SQL : itertools không (và nói chung không thể) sắp xếp trình lặp trước, vì vậy các nhóm có cùng "khóa" không được hợp nhất.


27

Một vi dụ khac:

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

kết quả trong

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

Lưu ý rằng igroup là một iterator (một iterator phụ như tài liệu gọi nó).

Điều này rất hữu ích để chunk một máy phát điện:

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)

Một ví dụ khác về nhóm - khi các khóa không được sắp xếp. Trong ví dụ sau, các mục trong xx được nhóm theo các giá trị trong yy. Trong trường hợp này, một tập hợp các số 0 được xuất ra đầu tiên, tiếp theo là một tập hợp các số 0, tiếp theo là một bộ số không.

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])

Sản xuất:

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

Điều đó thật thú vị, nhưng liệu itertools.islice có tốt hơn để chunk một iterable không? Nó trả về một đối tượng lặp như máy phát điện, nhưng nó sử dụng mã C.
trojjer

@trojjer islice sẽ tốt hơn NẾU các nhóm có kích thước phù hợp.
woodm1979 17/12/13

Tôi muốn nhận: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS

21

CẢNH BÁO:

Danh sách cú pháp (groupby (...)) sẽ không hoạt động theo cách bạn dự định. Nó dường như phá hủy các đối tượng lặp bên trong, vì vậy sử dụng

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

sẽ sản xuất:

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

Thay vào đó, trong danh sách (groupby (...)), hãy thử [(k, list (g)) cho k, g trong groupby (...)] hoặc nếu bạn thường xuyên sử dụng cú pháp đó,

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

và có được quyền truy cập vào chức năng nhóm trong khi tránh các trình lặp đó (cho dữ liệu nhỏ) tất cả cùng nhau.


3
Nhiều câu trả lời đề cập đến khối vấp ngã mà bạn phải sắp xếp trước khi nhóm để có kết quả mong đợi. Tôi vừa gặp câu trả lời này, điều này giải thích cho hành vi kỳ lạ mà tôi chưa từng thấy trước đây. Trước đây tôi chưa từng thấy bởi vì bây giờ tôi chỉ cố gắng liệt kê (nhóm (phạm vi (10)) như @singular nói. Trước đó tôi luôn sử dụng cách tiếp cận "được đề xuất" là lặp lại "thủ công" qua các đối tượng nhóm chứ không phải hãy để danh sách () constructor "tự động" làm việc đó
The Red Pea

9

Tôi muốn đưa ra một ví dụ khác trong đó nhóm không có sắp xếp không hoạt động. Chuyển thể từ ví dụ của 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 " "

đầu ra là

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.

Có hai nhóm với kịch bản, trong khi người ta chỉ mong đợi một nhóm


5
Bạn phải sắp xếp dữ liệu trước, sử dụng làm khóa cho chức năng bạn đang nhóm. Điều này được đề cập trong hai bài viết ở trên, nhưng không được làm nổi bật.
mbatchkarov

Tôi đã thực hiện một sự hiểu biết chính tả để bảo vệ các trình lặp phụ theo khóa, cho đến khi tôi nhận ra rằng điều này đơn giản như chính tả (nhóm (iterator, key)). Ngọt.
trojjer

Trên suy nghĩ thứ hai và sau khi thử nghiệm, cuộc gọi dict quấn quanh nhóm sẽ làm cạn kiệt các trình lặp phụ của nhóm. Chỉ trích.
trojjer

Điểm của câu trả lời này là gì? Làm thế nào nó được xây dựng trên câu trả lời ban đầu ?
codeforester

7

@CaptSolo, tôi đã thử ví dụ của bạn, nhưng nó không hoạt động.

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

Đầu ra:

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

Như bạn có thể thấy, có hai o và hai e, nhưng chúng được chia thành các nhóm riêng biệt. Đó là khi tôi nhận ra bạn cần sắp xếp danh sách được truyền cho hàm nhóm. Vì vậy, cách sử dụng đúng sẽ là:

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

Đầu ra:

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

Chỉ cần nhớ, nếu danh sách không được sắp xếp, chức năng nhóm sẽ không hoạt động !


7
Trên thực tế nó hoạt động. Bạn có thể nghĩ rằng hành vi này là hỏng, nhưng nó hữu ích trong một số trường hợp. Xem câu trả lời cho câu hỏi này để biết ví dụ: stackoverflow.com/questions/1553275/
Kẻ

6

Sắp xếp và nhóm

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

Làm cách nào để sử dụng itertools.groupby () của Python?

Bạn có thể sử dụng nhóm để nhóm các thứ để lặp đi lặp lại. Bạn cung cấp cho nhóm một iterable và một chức năng phím tùy chọn / có thể gọi được để kiểm tra các mục khi chúng ra khỏi iterable và nó trả về một iterator cung cấp hai lần kết quả của khóa có thể gọi được và các mục thực tế trong lặp đi lặp lại. Từ sự giúp đỡ:

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

Đây là một ví dụ về nhóm sử dụng coroutine để nhóm theo số đếm, nó sử dụng một khóa có thể gọi được (trong trường hợp này coroutine.send) để chỉ ra số đếm cho nhiều lần lặp và một bộ lặp phụ của các phần tử:

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))

in

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

1

Một ví dụ hữu ích mà tôi đã gặp có thể hữu ích:

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)

Đầu vào mẫu: 14445221

Đầu ra mẫu: (1,1) (3,4) (1,5) (2,2) (1,1)


1

Việc thực hiện cơ bản này đã giúp tôi hiểu chức năng này. Hy vọng nó cũng giúp người khác:

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

Bạn có thể viết chức năng nhóm riêng:

           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
phát minh lại bánh xe không phải là một ý tưởng tuyệt vời, câu hỏi cũng là để giải thích itertools nhóm, không viết riêng
user2678074

1
@ user2678074 Bạn nói đúng. Đó là một cái gì đó nếu bạn muốn viết riêng cho một quan điểm học tập.
Bầu trời

2
Cũng tốt hơn sử dụng một defaultdict (danh sách) để nó thậm chí còn ngắn hơn
Mickey Perlstein

@MickeyPerlstein và nhanh hơn.
funnydman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.