Jak korzystać z itertools.groupby ()?

506

Nie byłem w stanie znaleźć zrozumiałego wyjaśnienia, jak właściwie korzystać z itertools.groupby()funkcji Pythona . To, co próbuję zrobić, to:

  • Zrób listę - w tym przypadku dzieci zobiektywizowanego lxmlelementu
  • Podziel go na grupy na podstawie niektórych kryteriów
  • Później iteruj osobno po każdej z tych grup.

Przejrzałem dokumentację i przykłady , ale miałem problemy z zastosowaniem ich poza prostą listą liczb.

Jak więc korzystać z itertools.groupby()? Czy jest inna technika, której powinienem użyć? Docenione zostaną również wskaźniki dobrego „wstępnego” czytania.

James Sulak
źródło
jednym przydatnym przypadkiem byłoby leetcode.com/problems/string-compression
ShawnLee

Odpowiedzi:

655

WAŻNA UWAGA: Najpierw musisz posortować dane .


To, czego nie dostałem, to to, że w przykładowej konstrukcji

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

kjest bieżącym kluczem grupowania i gjest iteratorem, którego można użyć do iteracji w grupie zdefiniowanej przez ten klucz grupowania. Innymi słowy, groupbysam iterator zwraca iteratory.

Oto przykład tego, używając jaśniejszych nazw zmiennych:

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

To da ci wynik:

Niedźwiedź to zwierzę.
Kaczka to zwierzę.

Kaktus to roślina.

Łódź motorowa to pojazd.
Autobus szkolny to pojazd.

W tym przykładzie thingsjest to lista krotek, w której pierwszy element w każdej krotce jest grupą, do której należy drugi element.

groupby()Funkcja ma dwa parametry: (1) dane do grupy lub (2) funkcji do grupy, do której za pomocą.

lambda x: x[0]Mówi tutaj, groupby()aby użyć pierwszego elementu w każdej krotce jako klucza grupowania.

W powyższej forinstrukcji groupbyzwraca trzy pary (klucz, iterator grupy) - raz dla każdego unikalnego klucza. Za pomocą zwróconego iteratora można iterować poszczególne elementy w tej grupie.

Oto nieco inny przykład z tymi samymi danymi, wykorzystujący rozumienie listy:

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

To da ci wynik:

zwierzęta: niedźwiedź i kaczka.
rośliny: kaktus.
pojazdy: motorówka i autobus szkolny.

James Sulak
źródło
1
Czy istnieje sposób, aby wcześniej określić grupy, a następnie nie wymagać sortowania?
John Salvatier,
2
itertools zwykle klika dla mnie, ale miałem też „blok” dla tego. Doceniam twoje przykłady - znacznie jaśniejsze niż dokumenty. Myślę, że itertools mają tendencję do klikania lub nie, i są znacznie łatwiejsze do uchwycenia, jeśli masz podobne problemy. Nie potrzebowałem jeszcze tego na wolności.
Profane
3
@Julian python docs wydaje się świetny do większości rzeczy, ale jeśli chodzi o iteratory, generatory i cherrypy, dokumenty najbardziej mnie zaskakują. Dokumenty Django są podwójnie zaskakujące.
Marc Maxmeister
6
+1 za sortowanie - nie zrozumiałem, co masz na myśli, dopóki nie zgrupowałem moich danych.
Cody
4
@DavidCrook bardzo późno na imprezę, ale może komuś pomóc. Prawdopodobnie dlatego, że twoja tablica nie jest posortowana, spróbuj groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))założyć, że my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]i chcesz pogrupować wedługanimal or plant
Robin Nemeth,
71

Przykład w dokumentacji Pythona jest dość prosty:

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

Tak więc w twoim przypadku dane są listą węzłów, keyfuncgdzie logika funkcji kryteriów idzie, a następnie groupby()grupuje dane.

Zanim zadzwonisz, musisz uważnie posortować dane według kryteriów, groupbyaby nie zadziałały. groupbyMetoda faktycznie po prostu dokonuje iteracji listy i za każdym razem, gdy zmienia się klucz, tworzy nową grupę.

Seb
źródło
45
Więc przeczytałeś keyfunci powiedziałeś: „tak, wiem dokładnie, co to jest, ponieważ ta dokumentacja jest dość prosta.”? Niesamowite!
Jarad,
5
Wierzę, że większość ludzi już wie o tym „prostym”, ale bezużytecznym przykładzie, ponieważ nie mówi, jakiego rodzaju „danych” i „keyfunc” użyć !! Ale chyba też nie wiesz, inaczej pomógłbyś ludziom, wyjaśniając to, a nie tylko kopiując i wklejając. Czy ty
Apostolos
69

itertools.groupby to narzędzie do grupowania elementów.

Z dokumentów , zbieramy dalej, co może zrobić:

# [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 obiekty dają pary klucz-grupa, gdzie grupa jest generatorem.

funkcje

  • A. Grupuj kolejne elementy razem
  • B. Pogrupuj wszystkie wystąpienia elementu, biorąc pod uwagę posortowaną iterowalność
  • C. Określ sposób grupowania elementów za pomocą funkcji klucza *

Porównania

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

Używa

Uwaga: Kilka ostatnich przykładów pochodzi z PyCon Víctora Terróna (dyskusja) (hiszpański) , „Kung Fu at Dawn with Itertools”. Zobacz także groupbykod źródłowy napisany w C.

* Funkcja, w której wszystkie elementy są przekazywane i porównywane, wpływając na wynik. Inne obiekty z kluczowymi funkcjami obejmują sorted(), max()i min().


Odpowiedź

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
pylang
źródło
1
Technicznie rzecz biorąc, doktorzy powinni chyba powiedzieć [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq
1
Tak. Większość dokumentów itertools jest „skracanych” w ten sposób. Ponieważ wszystkie itertools są iteratorami, muszą być rzutowane na wbudowane ( list(), tuple()) lub używane w pętli / zrozumieniu, aby wyświetlić zawartość. Są to zwolnienia, które autor prawdopodobnie wykluczył w celu zaoszczędzenia miejsca.
pylang
39

Sztuczka neato z Groupby polega na uruchomieniu kodowania długości w jednym wierszu:

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

da ci listę 2-krotek, w których pierwszy element to znak, a drugi to liczba powtórzeń.

Edycja: Zauważ, że właśnie to się oddziela itertools.groupby od GROUP BYsemantyki SQL : itertools nie sortuje iteratora z góry, a więc grupy z tym samym „kluczem” nie są scalane.

nimish
źródło
27

Inny przykład:

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

prowadzi do

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

Zauważ, że igroup jest iteratorem (iteratorem, jak to nazywa dokumentacja).

Jest to przydatne do dzielenia generatora:

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)

Kolejny przykład grupowania - gdy klucze nie są posortowane. W poniższym przykładzie elementy w xx są pogrupowane według wartości w yy. W takim przypadku najpierw wyprowadzany jest jeden zestaw zer, a następnie zestaw zer, a następnie ponownie zestaw zer.

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

Produkuje:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]
użytkownik650654
źródło
To ciekawe, ale czy itertools.islice nie byłoby lepsze do dzielenia iterowalnego? Zwraca obiekt, który iteruje jak generator, ale używa kodu C.
trojjer
@trojjer islice byłby lepszy JEŻELI grupy mają stały rozmiar.
woodm1979,
Chcę uzyskać: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS
21

OSTRZEŻENIE:

Lista składni (groupby (...)) nie będzie działać w zamierzony sposób. Wydaje się, że niszczy wewnętrzne obiekty iteratora, więc używa

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

będzie produkować:

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

Zamiast listy (groupby (...)) spróbuj [(k, list (g)) dla k, g w groupby (...)] lub jeśli często używasz tej składni,

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

i uzyskaj dostęp do funkcji grupowania, unikając jednocześnie tych irytujących (dla małych danych) iteratorów.

RussellStewart
źródło
3
Wiele odpowiedzi odnosi się do przeszkody, którą należy posortować przed pogrupowaniem, aby uzyskać oczekiwane wyniki. Właśnie spotkałem się z tą odpowiedzią, która wyjaśnia dziwne zachowanie, którego wcześniej nie widziałam. Nie widziałem wcześniej, ponieważ dopiero teraz próbowałem wymienić (grupowanie (zakres (10)), jak mówi @singular. Wcześniej zawsze korzystałem z „zalecanego” podejścia polegającego na „ręcznym” iterowaniu obiektów grupujących zamiast pozwalanie konstruktorowi list () na „automatyczne” wykonanie tej czynności
Red Pea,
9

Chciałbym podać inny przykład, w którym funkcja grupowania bez sortowania nie działa. Na podstawie przykładu Jamesa Sulaka

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

wyjście jest

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.

są dwie grupy z pojazdem, podczas gdy można oczekiwać tylko jednej grupy

Kiriloff
źródło
5
Najpierw musisz posortować dane, używając jako klucza funkcji, którą grupujesz. Jest to wspomniane w dwóch postach powyżej, ale nie jest podświetlone.
mbatchkarov
Robiłem rozumienie dykt, aby zachować pod-iteratory według klucza, dopóki nie zdałem sobie sprawy, że jest to tak proste jak dykt (grupowanie (iterator, klucz)). Słodkie.
trojjer
Po zastanowieniu i po eksperymentach wywołanie dykta owinięte wokół grupy wyczerpuje pod-iteratory grupy. Cholera.
trojjer
Jaki jest sens tej odpowiedzi? Jak opiera się na oryginalnej odpowiedzi ?
codeforester
7

@CaptSolo, próbowałem twojego przykładu, ale to nie zadziałało.

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

Wynik:

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

Jak widać, są dwa o i dwa e, ale podzielili się na osobne grupy. Wtedy zdałem sobie sprawę, że musisz posortować listę przekazaną do funkcji grupowania. Prawidłowe użycie to:

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

Wynik:

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

Wystarczy pamiętać, że jeśli lista nie zostanie posortowana, funkcja grupowania nie będzie działać !

pedromanoel
źródło
7
Właściwie to działa. Możesz uważać to zachowanie za zepsute, ale w niektórych przypadkach jest przydatne. Zobacz odpowiedzi na to pytanie, na przykład: stackoverflow.com/questions/1553275/…
Denis Otkidach
6

Sortowanie i grupowanie

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'}
Satyajit Das
źródło
5

Jak korzystać z itertools.groupby () Pythona?

Możesz użyć groupby, aby pogrupować rzeczy do iteracji. Dajesz grupie opcję iterowalną oraz opcjonalną funkcję kluczową / wywoływalną, za pomocą której można sprawdzać elementy wychodzące z iterowalnej, i zwraca iterator, który daje dwukrotność wyniku kluczowego wywołania i rzeczywistych pozycji w kolejna iterowalna. Z pomocy:

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

Oto przykład grupowania za pomocą coroutine w celu grupowania według liczby, używa klucza wywoływalnego (w tym przypadku coroutine.send), aby po prostu wyliczyć liczbę dla dowolnej liczby iteracji i zgrupowanego pod-iteratora elementów:

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

odciski

[(0, [0, 1, 2]), (1, [3, 4, 5]), (2, [6, 7, 8]), (3, [9])]
Aaron Hall
źródło
1

Pomocny może być jeden przydatny przykład:

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)

Przykładowe dane wejściowe: 14445221

Wyjściowa próbka: (1,1) (3,4) (1,5) (2,2) (1,1)

Arko
źródło
1

Ta podstawowa implementacja pomogła mi zrozumieć tę funkcję. Mam nadzieję, że pomaga to również innym:

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
Tiago
źródło
0

Możesz napisać własną funkcję grupowania:

           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]}
Niebo
źródło
1
ponowne wymyślenie koła nie jest świetnym pomysłem, również pytanie polega na wyjaśnieniu itertools groupby, a nie pisaniu własnego
user2678074
1
@ user2678074 Masz rację. Jest to coś, jeśli chcesz napisać własny z naukowego punktu widzenia.
Niebo
2
Lepiej też użyj defaultdict (listy), aby był jeszcze krótszy
Mickey Perlstein,
@MickeyPerlstein i szybciej.
funnydman