Verständnisfrage: itertools.groupby

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich hantiere gerade mit dem Modul itertool herum, und bin dabei auf die groupby()-Funktion gestoßen.

Im Laufe meiner Recherche habe ich mir folgenden Schnipsel zusammengebastelt:

Code: Alles auswählen

from itertools import groupby

strs="AaaBbbCcccDde"
groups = groupby(strs)

print "result", [(label, sum(1 for i in group)) for label, group in groups]
# output: [('A', 1), ('a', 2), ('B', 1), ('b', 2), ('C', 1), ('c', 3), ('D', 1), ('d', 1), ('e', 1)]
Natürlich möchte ich den Schnipsel in seinen Einzelheiten verstehen. Da mir dieser Ausdruck hinter der Print-Anweisung verdächtig nach einer List Comprehension aussieht, habe ich mir als "Muster" eine Formal angelegt:

Code: Alles auswählen

#       M = [x for x in S]
Also fing ich an, den oberen Ausdruck zu zerlegen:
Innerhalb der LC gibt es eine For-Schelife: for label, group in groups, dies entspricht aus dem Muster for x in S. Hier sehe ich, dass die groupby-Funktion zwei Werte zurückliefert, die einmal in die Variable label und einmal in die Variable group gespeichert werden.

Da die LC eine neue Liste erzeugt, wo dann die neuen Elemente gespeichert werden, braucht die LC vor der For-Schleife einen Ausdruck. Im Muster wäre hier das grüne x --> [x for x in S]. Im Schnipsel wäre die rote Markierung der Ausdruck in der LC --> [(label, sum(1 for i in group)) for label, group in groups]

Wenn ich einmal den Ausdruck extrahiere: (label, sum(1 for i in group))
Hier sehe ich, dass ein Tupel erzeugt wird. Dies erkenne ich an den äußeren runden Klammern. Und innerhalb des Tupels sehe ich, dass eine Generator Comprehension angewendet wird - und zwar hier --> sum(1 for i in group). Und in der Variable label innerhalb des Tupels werden die einzelnen Buchstaben gespeichert. Als Zwischenfazit sehe ich, dass in diesem Tupel als "Paare" an Daten gespeichert werden.

Nun habe ich diesen Schnipsel für mich zerlegt, und versuche das mal in Worten zusammezufassen:
In der LC wird gesagt: Iteriere mir über groups und speichere die Werte einmal in label und group. Erzeuge mir eine neue Liste, um Werte dort abzuspeichern, und greife daher auf den Ausdruck (label, sum(1 for i in group)) zu. In der neuen Liste sollen Tupels mit Daten-Paare gespeichert werden. Innerhalb des Tupel-Ausdruckes wird die Generator Comprehension angesprochen. Da der GC einen Iterator zurückgibt, und der Iterator somit eine sogenannte next-Methode besitzt, wird im GC immer eins hochgezählt (daher auch die Zahl 1) - solange bis der Iterator durchlaufen ist. Und das ganze wird dann summiert. Und in der Variable label werden die einzelnen Buchstaben gespeichert. Am Ende werden dann in der der neuen Liste Tupels gespeichert, die wiederum Daten-Paare enthalten. Hier werden also die Buchstaben gezählt, wie oft sie im String vorkamen.

Habe ich das richtig gedeutet? Bestimmt habe ich mal wieder daneben gegriffen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: stimmt soweit alles.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Danke. Wenn ich diesen Schnipsel runterbrechen würde, käme ich zum folgenden Ergebnis:

Code: Alles auswählen

strs = "AaaBbbCcccDde"
print "Your String: ", strs
for label, group in groupby(strs):
    for grouped_char in group:
        print "Grouped char",  grouped_char

        print "Total", 1+(sum(1 for i in group))

    print "------"
Output:
Your String: AaaBbbCcccDde
Grouped char A
Total 1
------
Grouped char a
Total 2
------
Grouped char B
Total 1
------
Grouped char b
Total 2
------
Grouped char C
Total 1
------
Grouped char c
Total 3
------
Grouped char D
Total 1
------
Grouped char d
Total 1
------
Grouped char e
Total 1
------
Meine naive Frage an dieser Stelle. Wäre der runtergebrochene Quelltext nicht wesentlich Speicher schonender als im ersten Beispiel? Denn im ersten Beispiel wird ja eine neue Liste erzeugt. In meinem Beispiel wird keine neue Liste erzeugt (oder übersehe ich was?). Gut, mein Quelltext ist etwas "umständlicher" und somit mit mehr Lese- und SChreibaufwand verbunden, da hier mehr verschachtelt ist. Aber aus reiner technischer Perspektive: wäre mein zweiter Quelltext nicht eher "schonender"? (Die Ausführungsgeschwindigkeit vernachlässigen wir einmal - wobei ich nicht glaube, dass diese runter gebrochene Variante wesentlich langsamer sein sollte.)
BlackJack

@Sophus: Dein runtergebrochenes Beispiel ist nicht richtig weil die ``for``-Schleife in Zeile 4 immer genau einmal durchlaufen wird, und damit ist die Schleife sinnfrei, denn die wiederholt ja nie irgendetwas.

Was das speicherschonender angeht: Ja (wenn da entsprechende viele Elemente verarbeitet werden), aber im ersten Beispiel wird ja eine Liste erzeugt. Das scheint Absicht zu sein, sonst hätte man das ja dort nicht gemacht sondern Zeile 6 so geschrieben:

Code: Alles auswählen

for item in ((label, sum(1 for _ in group)) for label, group in groups):
    print item
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: was willst Du mit der zweiten for-Schleife bezwecken? Wozu ist label da?

Code: Alles auswählen

strs = "AaaBbbCcccDde"
print "Your String: ", strs
for label, group in groupby(strs):
    print "Grouped char",  label
    print "Total", sum(1 for _ in group)
    print "------"
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sophus: In Zeile 6 Deines ersten Beispiels befindet sich eine List-Comprehension, da bei einem speicherschonenden Generator-Ausdruck das print Statement sonst nur die Adresse des Generators ausgeben würde und dieser nie anlaufen würde. BlackJacks Variante wäre die konsequente Umsetzung dieses Beispiels.

Um das Problem in Deinem zweiten Beispielcode zu verstehen, kann es hilfreich sein wenn Du Dich fragst, warum es erforderlich ist zur Summenbildung in Zeile 7 eine 1 zu addieren.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack, Sirius3 und kbr: Ihr habt Recht. Die zweite For-Schleife im zweiten Beispiel war wirklich unnötig. Sehe ich jetzt auch erst. Keine Ahnung, was mich da geritten hat. Manchmal sollte ich mir einen Quelltext zwei Mal mehr ansehen.

Allerdings habe ich eine Frage an Sirius3 (eigentlich an alle!).

Wieso steht in Sirius3' Beispiel ein Bodenstrich im GC? Hat es eine "besondere" Wirkung?

Code: Alles auswählen

sum(1 for _ in group)
          ^
          #Hiere
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, hat es nicht. Es steht nur fuer "ein Bezeichner den ich nicht wirklich zu benutzen gedenke". Reine Konvention. Wird so zB von pylint etc. interpretiert & nicht angemeckert, wenn du den nicht benutzt.

Und in diesem Fall interessiert niemanden der Inhalt von group.
Antworten