Wie verwende ich itertools.groupby ()?

507

Ich konnte keine verständliche Erklärung für die tatsächliche Verwendung der Python- itertools.groupby()Funktion finden. Ich versuche Folgendes zu tun:

  • Nehmen Sie eine Liste - in diesem Fall die untergeordneten lxmlElemente eines objektivierten Elements
  • Teilen Sie es anhand einiger Kriterien in Gruppen ein
  • Dann iterieren Sie später über jede dieser Gruppen separat.

Ich habe die Dokumentation und die Beispiele überprüft , aber ich hatte Probleme, sie über eine einfache Liste von Zahlen hinaus anzuwenden.

Also, wie benutze ich von itertools.groupby()? Gibt es eine andere Technik, die ich verwenden sollte? Hinweise auf eine gute "Voraussetzung" des Lesens wären ebenfalls willkommen.

James Sulak
quelle
Ein nützlicher Fall für das wäre leetcode.com/problems/string-compression
ShawnLee

Antworten:

656

WICHTIGER HINWEIS: Sie müssen zuerst Ihre Daten sortieren .


Der Teil, den ich nicht bekommen habe, ist der in der Beispielkonstruktion

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

kist der aktuelle Gruppierungsschlüssel und gein Iterator, mit dem Sie die durch diesen Gruppierungsschlüssel definierte Gruppe durchlaufen können. Mit anderen Worten, der groupbyIterator selbst gibt Iteratoren zurück.

Hier ist ein Beispiel dafür mit klareren Variablennamen:

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

Dies gibt Ihnen die Ausgabe:

Ein Bär ist ein Tier.
Eine Ente ist ein Tier.

Ein Kaktus ist eine Pflanze.

Ein Schnellboot ist ein Fahrzeug.
Ein Schulbus ist ein Fahrzeug.

In diesem Beispiel thingshandelt es sich um eine Liste von Tupeln, bei denen das erste Element in jedem Tupel die Gruppe ist, zu der das zweite Element gehört.

Die groupby()Funktion benötigt zwei Argumente: (1) die zu gruppierenden Daten und (2) die Funktion, mit der sie gruppiert werden sollen.

Hier wird angegeben, lambda x: x[0]dass groupby()das erste Element in jedem Tupel als Gruppierungsschlüssel verwendet werden soll.

Gibt in der obigen forAnweisung groupbydrei Paare (Schlüssel, Gruppeniterator) zurück - einmal für jeden eindeutigen Schlüssel. Mit dem zurückgegebenen Iterator können Sie jedes einzelne Element in dieser Gruppe durchlaufen.

Hier ist ein etwas anderes Beispiel mit denselben Daten unter Verwendung eines Listenverständnisses:

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

Dies gibt Ihnen die Ausgabe:

Tiere: Bär und Ente.
Pflanzen: Kaktus.
Fahrzeuge: Schnellboot und Schulbus.

James Sulak
quelle
1
Gibt es eine Möglichkeit, die Gruppen im Voraus anzugeben und dann nicht zu sortieren?
John Salvatier
2
itertools klickt normalerweise für mich, aber ich hatte auch einen 'Block' für diesen. Ich habe Ihre Beispiele geschätzt - viel klarer als Dokumente. Ich denke, itertools neigen dazu, entweder zu klicken oder nicht, und sind viel einfacher zu verstehen, wenn Sie auf ähnliche Probleme gestoßen sind. Ich habe diesen in freier Wildbahn noch nicht gebraucht.
Profane
3
@ Julian Python-Dokumente scheinen für die meisten Dinge großartig zu sein, aber wenn es um Iteratoren, Generatoren und Cherrypy geht, mystifizieren mich die Dokumente meistens. Djangos Dokumente sind doppelt verwirrend.
Marc Maxmeister
6
+1 für die Sortierung - Ich habe nicht verstanden, was Sie meinten, bis ich meine Daten gruppiert habe.
Cody
4
@ DavidCrook sehr spät zur Party, könnte aber jemandem helfen. Es ist wahrscheinlich, weil Ihr Array nicht sortiert ist. Versuchen Sie es groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))unter der Annahme, dass my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]und Sie gruppieren möchten nachanimal or plant
Robin Nemeth
72

Das Beispiel in den Python-Dokumenten ist recht einfach:

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

In Ihrem Fall handelt es sich bei Daten also um eine Liste von Knoten. Hier keyfuncwird die Logik Ihrer Kriterienfunktion groupby()gespeichert und anschließend werden die Daten gruppiert.

Sie müssen darauf achten, die Daten nach den Kriterien zu sortieren, bevor Sie anrufen, sonst groupbyfunktioniert es nicht. groupbyDie Methode durchläuft tatsächlich nur eine Liste und erstellt bei jeder Änderung des Schlüssels eine neue Gruppe.

Seb
quelle
46
Sie haben also gelesen keyfuncund sagten: "Ja, ich weiß genau, was das ist, weil diese Dokumentation recht einfach ist." Unglaublich!
Jarad
5
Ich glaube, die meisten Leute wissen bereits über dieses "unkomplizierte", aber nutzlose Beispiel Bescheid, da es nicht sagt, welche Art von "Daten" und "Schlüsselfunktion" zu verwenden ist !! Aber ich denke, Sie wissen es auch nicht, sonst würden Sie den Leuten helfen, indem Sie es klären und nicht nur kopieren. Oder tust du?
Apostolos
69

itertools.groupby ist ein Werkzeug zum Gruppieren von Elementen.

Aus den Dokumenten entnehmen wir weiter, was es tun könnte:

# [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 Objekte ergeben Schlüssel-Gruppen-Paare, bei denen die Gruppe ein Generator ist.

Eigenschaften

  • A. Gruppieren Sie aufeinanderfolgende Elemente
  • B. Gruppieren Sie alle Vorkommen eines Elements mit einer sortierten Iterierbarkeit
  • C. Geben Sie an, wie Elemente mit einer Schlüsselfunktion gruppiert werden sollen *

Vergleiche

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

Verwendet

Hinweis: Einige der letzteren Beispiele stammen aus Víctor Terróns PyCon (Diskussion) (Spanisch) "Kung Fu at Dawn with Itertools". Siehe auch den in C geschriebenen groupbyQuellcode .

* Eine Funktion, bei der alle Elemente durchlaufen und verglichen werden, um das Ergebnis zu beeinflussen. Andere Objekte mit Schlüsselfunktionen umfassen sorted(), max()und min().


Antwort

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
Pylang
quelle
1
Technisch sollten die Dokumente wahrscheinlich sagen [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq
1
Ja. Die meisten itertools-Dokumentzeichenfolgen werden auf diese Weise "gekürzt". Da alle itertools Iteratoren sind, müssen sie in ein eingebautes ( list(), tuple()) umgewandelt oder in einer Schleife / einem Verständnis verwendet werden, um den Inhalt anzuzeigen. Dies sind Redundanzen, die der Autor wahrscheinlich ausgeschlossen hat, um Platz zu sparen.
Pylang
39

Ein netter Trick mit groupby besteht darin, die Längencodierung in einer Zeile auszuführen:

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

Sie erhalten eine Liste mit 2 Tupeln, wobei das erste Element das Zeichen und das zweite die Anzahl der Wiederholungen ist.

Bearbeiten: Beachten Sie, dass dies itertools.groupbyvon der SQL- GROUP BYSemantik abweicht: itertools sortiert den Iterator nicht im Voraus (und kann ihn im Allgemeinen nicht sortieren), sodass Gruppen mit demselben "Schlüssel" nicht zusammengeführt werden.

nimish
quelle
27

Ein anderes Beispiel:

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

führt zu

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

Beachten Sie, dass igroup ein Iterator ist (ein Unteriterator, wie es in der Dokumentation genannt wird).

Dies ist nützlich, um einen Generator zu zerlegen:

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)

Ein weiteres Beispiel für groupby - wenn die Schlüssel nicht sortiert sind. Im folgenden Beispiel werden Elemente in xx nach Werten in yy gruppiert. In diesem Fall wird zuerst ein Satz von Nullen ausgegeben, gefolgt von einem Satz von Einsen, gefolgt von einem Satz von Nullen.

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

Produziert:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]
user650654
quelle
Das ist interessant, aber wäre itertools.islice nicht besser, um eine iterable zu zerlegen? Es gibt ein Objekt zurück, das wie ein Generator iteriert, aber C-Code verwendet.
Trojjer
@trojjer islice wäre besser, wenn die Gruppen eine einheitliche Größe haben.
Woodm1979
Ich möchte bekommen: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS
21

WARNUNG:

Die Syntaxliste (groupby (...)) funktioniert nicht wie beabsichtigt. Es scheint die internen Iterator-Objekte zu zerstören, also mit

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

wird herstellen:

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

Versuchen Sie stattdessen anstelle von list (groupby (...)) [(k, list (g)) für k, g in groupby (...)], oder wenn Sie diese Syntax häufig verwenden,

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

und erhalten Sie Zugriff auf die Groupby-Funktionalität, während Sie diese lästigen (für kleine Daten) Iteratoren insgesamt vermeiden.

RussellStewart
quelle
3
Viele der Antworten beziehen sich auf den Stolperstein, den Sie vor der Gruppierung sortieren müssen, um die erwarteten Ergebnisse zu erhalten. Ich bin gerade auf diese Antwort gestoßen, die das seltsame Verhalten erklärt, das ich vorher noch nicht gesehen habe. Ich habe es noch nie gesehen, weil ich erst jetzt versucht habe, (groupby (range (10)) aufzulisten, wie @singular sagt. Vorher hatte ich immer den "empfohlenen" Ansatz verwendet, "manuell" durch die groupby-Objekte zu iterieren, anstatt Lassen Sie den list () Konstruktor "automatisch" tun.
The Red Pea
9

Ich möchte ein weiteres Beispiel geben, bei dem groupby ohne Sortierung nicht funktioniert. Nach Beispiel von 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 " "

Ausgabe ist

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.

Es gibt zwei Gruppen mit Fahrzeugen, während man nur eine Gruppe erwarten kann

kiriloff
quelle
5
Sie müssen zuerst die Daten sortieren und als Funktion die Funktion verwenden, nach der Sie gruppieren. Dies wird in zwei Beiträgen oben erwähnt, aber nicht hervorgehoben.
mbatchkarov
Ich machte ein Diktatverständnis, um die Unteriteratoren nach Schlüssel zu erhalten, bis mir klar wurde, dass dies so einfach wie Diktat war (groupby (Iterator, Schlüssel)). Süss.
Trojjer
Beim zweiten Gedanken und nach dem Experimentieren erschöpft der Diktataufruf, der um die Gruppe gewickelt ist, die Gruppenunteriteratoren. Verdammt.
Trojjer
Was ist der Sinn dieser Antwort? Wie baut es auf der ursprünglichen Antwort auf ?
Codeforester
7

@CaptSolo, ich habe dein Beispiel ausprobiert, aber es hat nicht funktioniert.

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

Ausgabe:

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

Wie Sie sehen können, gibt es zwei O's und zwei E's, aber sie wurden in separate Gruppen eingeteilt. Da wurde mir klar, dass Sie die an die groupby-Funktion übergebene Liste sortieren müssen. Die richtige Verwendung wäre also:

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

Ausgabe:

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

Denken Sie daran, wenn die Liste nicht sortiert ist, funktioniert die Groupby-Funktion nicht !

pedromanoel
quelle
7
Eigentlich funktioniert es. Sie könnten dieses Verhalten als fehlerhaft betrachten, aber es ist in einigen Fällen nützlich. Siehe Antworten auf diese Frage für ein Beispiel: stackoverflow.com/questions/1553275/…
Denis Otkidach
6

Sortieren und Gruppieren nach

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
quelle
5

Wie verwende ich Pythons itertools.groupby ()?

Sie können groupby verwenden, um Dinge zu gruppieren, über die iteriert werden soll. Sie geben groupby eine iterable und eine optionale Schlüsselfunktion / callable, mit der Sie die Elemente überprüfen können, wenn sie aus der iterable herauskommen, und es wird ein Iterator zurückgegeben, der ein Zwei-Tupel des Ergebnisses der aufrufbaren Taste und der tatsächlichen Elemente in gibt eine andere iterable. Aus der Hilfe:

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

Hier ist ein Beispiel für die Gruppierung mithilfe einer Coroutine zum Gruppieren nach einer Zählung. Dabei wird ein Schlüssel verwendet, der (in diesem Fall coroutine.send) aufgerufen werden kann, um die Zählung für so viele Iterationen und einen gruppierten Unteriterator von Elementen auszuspucken:

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

druckt

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

Ein nützliches Beispiel, auf das ich gestoßen bin, kann hilfreich sein:

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)

Beispieleingabe: 14445221

Probenausgabe: (1,1) (3,4) (1,5) (2,2) (1,1)

Arko
quelle
1

Diese grundlegende Implementierung hat mir geholfen, diese Funktion zu verstehen. Hoffe, es hilft auch anderen:

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
quelle
0

Sie können eine eigene Groupby-Funktion schreiben:

           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]}
Himmel
quelle
1
Rad neu zu erfinden ist keine gute Idee, auch die Frage ist, itertools groupby zu erklären, nicht selbst zu schreiben
user2678074
1
@ user2678074 Du hast recht. Es ist etwas, wenn Sie für eine Lernperspektive selbst schreiben möchten.
Himmel
2
Verwenden Sie auch besser ein Standarddiktat (Liste), damit es noch kürzer ist
Mickey Perlstein
@ MickeyPerlstein und schneller.
Funnydman