Listen zusammenfassen

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
patmaster
User
Beiträge: 106
Registriert: Donnerstag 3. Februar 2011, 17:21

Hi,

Ich habe gerade erst mit Python begonnen (2.7) und habe eine, zumindest für mich recht knifflige Frage.

ich habe ein Dic das so aussieht:

defaultdict(<type 'list'>, {'Dumme G\xc3\xa4nge revisited': [['Absatzkontrolle', '300'], ['Absatzkontrolle', '60'], ['Absatzkontrolle', '154'], 514], 'Das Sparschwein im Wandel der Zeiten': [['Konvertierungsanleitung', '60'], ['Sonstiges', '120'], 180], 'Schlager 1': [['Konvertierungsanleitung', '150'], 150], 'RWZ 1': [['Absatzkontrolle', '170'], ['Konvertierungsanleitung', '180'], ['Absatzkontrolle', '60'], 410], 'RWZ 3': [['Absatzkontrolle', '55'], ['Konvertierungsanleitung', '60'], 115]})

Also es gibt zu jedem Key eine Liste die mind. eine weitere Liste enthält und eine Zahl enthält.
Nun kann es auch sein das diese Liste mehrere Listen enthält wie zb gleich beim Ersten Key ("Dumme...").

Diese Listen unterscheiden sich manchmal nur durch die Zahl die an der 2ten Position steht.
Ich würde nun gerne genau solche Listen zusammenfassen, wobei die Zahlen immer Addiert werden.

Um beim 1. Key zu bleiben:

aus

'Dumme G\xc3\xa4nge revisited': [['Absatzkontrolle', '300'], ['Absatzkontrolle', '60'], ['Absatzkontrolle', '154'], 514]

sollte dann

'Dumme G\xc3\xa4nge revisited': [['Absatzkontrolle', 514'], 514]

Ich hoffe ich habe mich verständlich ausgedrückt.

Ich habe versucht die SuFu zu verwenden, aber hab nicht wirklich etwas gefunden. Vlt. hab ichs auch einfach net verstanden weil ich erst seit 1 Monat Python lerne.
BlackJack

@patmaster: Es wäre ja fast einfach, wenn da nicht die Zahl am Ende in der Liste wäre. In Listen sollte man möglichst nur "gleichartige" Elemente stecken, damit man diese verarbeiten kann ohne den Typ von Elementen testen zu müssen und dann basierend auf dem Typ etwas bestimmtes zu tun oder es eben zu lassen.

Du musst das Problem auf kleinere Teilprobleme runter brechen. Das grosse Problem was Du uns hier zeigst, ist sicher noch nicht das Minimalproblem wo Du Deine Schwierigkeiten hast. Denn für das Zusammenfassen von ``[['Absatzkontrolle', '300'], ['Absatzkontrolle', '60'], ['Absatzkontrolle', '154'], 514]`` zu [['Absatzkontrolle', '514'], 514]`` ist es zum Beispiel egal dass das ein Wert in einem Dictionary ist.

Letztlich solltest Du aber erst einmal die Datenstruktur überdenken, so dass nicht verschiedene "duck types" in der gleichen Liste stecken.

Und Dein erster Wert ist auch kein gutes Beispiel, denn wenn man das löst, stösst man nicht auf Fragen die zum Beispiel der Wert zu 'RWZ 1' aufwirft. Wie sollte denn da das Ergebnis aussehen? Sollen die beiden 'Absatzkontrolle'-Listen zusammengefasst werden? Wenn ja, soll das Ergebnis vor oder nach der 'Konvertierungsanleitung'-Liste stehen? Falls die Reihenfolge dieser Listen grundsätzlich egal ist und gleiche erste Elemente grundsätzlich zusammengefasst werden sollen, dann sieht das irgendwie eher nach einem Kandidaten für ein Dictionary aus. Auch ein gerne wieder ein `defaultdict` wenn Du Dich dazu durchringen könntest Zahlen auch als solche zu speichern und nicht als Zeichenketten.
ws
User
Beiträge: 65
Registriert: Freitag 16. Juni 2006, 13:19

Hallo Patmaster,

vielleicht so ähnlich. Du musst das natürlich für Dich anpassen, und eventuell willst Du Dir auch noch die Reihenfolge der "Listennamen" merken. Und optimal ist das wahrscheinlich nicht, evtl. gibt's da auch bessere Möglichkeiten in Python 2.7 (bin noch mit 2.5/6 unterwegs).

Gruss

Wolfgang

Code: Alles auswählen

srcList = [['Absatzkontrolle', '300'], ['Absatzkontrolle', '60'], ['Absatzkontrolle', '154'], 514]


value_2_name = dict()

for li in srcList:
    if type(li) != list:
        break
    name, val = li
    prevVal = value_2_name.setdefault(name, 0)
    value_2_name[name] = prevVal + int(val)
    

for name in value_2_name:
    print name, value_2_name[name]
ws
User
Beiträge: 65
Registriert: Freitag 16. Juni 2006, 13:19

Bezüglich der Kommentare von BlackJack: er hat in allem was er sagt recht. Insofern kann Dir mein Code vielleicht helfen, den Einsatz von setdefault() zu verstehen, ansonsten solltest Du aber Deine Datenstruktur überarbeiten und so nicht weitermachen - das rächt sich bitterlich.
BlackJack

@ws: Wobei der Einsatz von `setdefault()` hier nicht wirklich "richtig" ist. Die Methode setzt den Wert im Dictionary ja auch tatsächlich, dass ist aber unnötig. Ein ``value_2_name.get(name, 0)`` hätte es auch getan.
BlackJack

@patmaster: Muss die letzte Zahl in den Listen denn da überhaupt unbedingt stehen? Denn es sieht zumindest bei den gezeigten Daten so aus, dass das immer die Summe der "Zahlen" in den zweielementigen Listen ist. Damit wäre das redundant und könnte einfach berechnet werden.
ws
User
Beiträge: 65
Registriert: Freitag 16. Juni 2006, 13:19

@BlackJack: Jetzt wo Du's sagst ...
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Und gnade einem $Gott, wenn man Unterklassen von `list` nutzt ;) Das `if type(..) != list` sollte man durch `if not isinstance(..)` austauschen.
problembär

patmaster hat geschrieben:ich habe ein Dic das so aussieht:

defaultdict(<type 'list'>, {'Dumme G\xc3\xa4nge revisited': [['Absatzkontrolle', '300'], ['Absatzkontrolle', '60'], ['Absatzkontrolle', '154'], 514], 'Das Sparschwein im Wandel der Zeiten': [['Konvertierungsanleitung', '60'], ['Sonstiges', '120'], 180], 'Schlager 1': [['Konvertierungsanleitung', '150'], 150], 'RWZ 1': [['Absatzkontrolle', '170'], ['Konvertierungsanleitung', '180'], ['Absatzkontrolle', '60'], 410], 'RWZ 3': [['Absatzkontrolle', '55'], ['Konvertierungsanleitung', '60'], 115]})
In dem Zusammenhang sollte man auch mal lobend erwähnen, wie einfach sowas in Python ist!

Wenn Du eine solche Datenstruktur z.B. mal in Perl versuchtest

http://perldoc.perl.org/perllol.html

wärst Du wahrscheinlich noch schockierter. ;)
Daß Python das ohne zu meckern macht, ist für mich fast schon ein Wunder.

Gruß
patmaster
User
Beiträge: 106
Registriert: Donnerstag 3. Februar 2011, 17:21

Erstmal vielen Dank für eure Antworten.
Ich werde den Code heute gleich mal probieren.

Ja die Zahl muss sein da ich das ganze an ein Django-template Übergebe und dort das Berechnen garnicht oder nur sehr umständlich funktioniert.

Ich weiß das die Datenstruktur ziemlich umständlich ist, aber das Arbeiten mit Django, so scheint es mir erfordet es teilweise Umwege zu gehen um ans Ziel zu kommen.

Ich werde aufjedenfall berichten ob der Code funkioniert...

/edit:

Okay der Code funzt leider nicht wirklich.

Wenn ich dein Beispiel hernehme (ohne was zu ändern) sieht das Ergebnis so aus:

{'Konvertierungsanleitung': 60, 'Absatzkontrolle': 55}

Vlt. soll ich mal Posten wie das gesamte dict am Ende aussehen sollte ?!:


defaultdict(<type 'list'>, {'Dumme G\xc3\xa4nge revisited': [['Absatzkontrolle', '514'], 514], 'Das Sparschwein im Wandel der Zeiten': [['Konvertierungsanleitung', '60'], ['Sonstiges', '120'], 180], 'Schlager 1': [['Konvertierungsanleitung', '150'], 150], 'RWZ 1': [['Absatzkontrolle', '230'], ['Konvertierungsanleitung', '180'], 410], 'RWZ 3': [['Absatzkontrolle', '55'], ['Konvertierungsanleitung', '60'], 115]})

Also ich will nur Innerhalb der value Liste Listen zusammenfassen bei denen der 1. Wert übereinstimmt und ihre 2. Werte addieren.
BlackJack

@patmaster: Anstelle dieser unübersichtlich verschachtelten Struktur aus "Grunddatentypen" könntest Du eine eigene Klasse schreiben die das kapselt und ein berechnetes Attribut (`property()`) für die Gesamtzahl hat.

Den eigentlich müsste die Struktur -- wie schon gesagt -- noch einen Tick komplexer sein, damit man die zweielementigen Listen verarbeiten kann ohne dass man über die Zahl am Ende der umschliessenden Liste stolpert und dafür eine unschöne Sonderbehandlung programmieren muss.

Der Code von ws funktioniert schon -- er ist halt nur ein Baustein zu einer kompletten Lösung. Ein wenig Arbeit musst Du also schon noch rein stecken. :-)

Ist die Reihenfolge nun eigentlich egal? Also dürfte der Wert zu 'RWZ 1' am Ende auch so aussehen ``[['Konvertierungsanleitung', '180'], ['Absatzkontrolle', '230'], 410]``?
patmaster
User
Beiträge: 106
Registriert: Donnerstag 3. Februar 2011, 17:21

Ja die Reihenfolge wäre egal.

Ich hab schon versucht das Ganze einwenig anders aufzubauen, aber irgendwie will mir das nicht wirklich gelingen.

Anfangen tut eigenltich alles mit einer Liste von Objecten aus einer Datenbank.
All diese Objekte haben natürlich Attribute, folgende sind wichtig:

Produkt
Minuten
Aktivitaet

Was ich brauche ist etwas (liste, dict, ...) wo 1. nach Produkt gruppiert und innerhalb der "Produktgruppe" nach Aktivität gruppiert wird. Also das alle Minuten die zur gleichen Aktivitaet im gleichen Produkt gehören addiert werden.

Nach Produkt habe ich ja schon Gruppiert nur kam dabei halt dieses Konstrukt raus :)
BlackJack

@patmaster: Wenn Du das Problem schon einmal für die Produktgruppe gelöst hast, dann kannst Du das für die Aktivität doch genau so machen -- mit einem `defaultdict`. Und vielleicht gleich beim erstellen der Datenstruktur ohne dass Du das Zwischenergebnis hast, was Du da gezeigt hast.
deets

Na dann:

Code: Alles auswählen

import pprint
from operator import itemgetter
from itertools import groupby

data = [
    ("foo", "mill", 10.0),
    ("foo", "mill", 20.0),
    ("foo", "lathe", 10.0),
    ("bar", "lathe", 5.0),
    ]



res = {}
for product, activities in groupby(data, itemgetter(0)):
    h = {}
    for activity, times in groupby(((activity, time) for
                                   _, activity, time in activities), itemgetter(0)):
        h[activity] = sum(time for _, time in times)
    res[product] = h


pprint.pprint(res)

Ich gebe zu, dass ist schon ein bisschen Fortgeschritten - aber wie ich finde zeigt es mal wieder, wie elegant Python sowas loesen kann.

du kannst natuerlich auch schon gleich versuchen, mehr in der DB machen zu lassen. Gruppieren und Summieren kann die auch. Aber die Syntax dafuer kaeme mir aus dem Kopp nicht richtig raus.
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

@deets: Als ich deine Lösung gesehen habe, kam gleich der Gedanke defaultdict zu verwenden

Code: Alles auswählen

from collections import defaultdict 

data = [
    ("foo", "mill", 10.0),
    ("foo", "mill", 20.0),
    ("foo", "lathe", 10.0),
    ("bar", "lathe", 5.0),
]

res = defaultdict(lambda: defaultdict(float))
for product, activity, time in data:
    res[product][activity] += time
Edit: Sorry, hatte die ganze Vorgeschichte garnicht gelesen. Da wurde ja bereits erwähnt defaultdict zu verwenden.
Zuletzt geändert von Zap am Freitag 11. März 2011, 13:13, insgesamt 1-mal geändert.
deets

@Zap: defenitiv besser. Ich muss gestehen, ich war bei dem groupby etwas ueberrascht, dass es zwar gruppiert, aber dann die Gruppen immer noch den Key enthalten. Allerdings ist das auch logisch, wie soll man's sonst auch machen...

Und deine Loesung ist robuster, denn sie setzt nicht vorraus, dass die Daten sortiert sind.

Und last but not least wusste ich nicht, dass float() == .0 - ich haette da lambda: 0.0 gebaut fuer.
Antworten