Aufeinanderfolgende Schlüssel für ein Dict aus einer Liste

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
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

Ich stehe gerade vor der Schwierigkeit, aus einer Liste Schlüssel für ein Dictionary zu machen und den entstandenen Schlüsseln Werte zuzuweisen.

Der Code findet sich (hoffentlich) hier.

Der auskommentierte Teil soll (aus vermutlich offenkundigen Gründen :-) ) den nichtkommentierten ersetzen. Leider tut er es nicht, (vermutlich) weil die "aufaddierten" Listeneinträge nicht als aufeinanderfolgende Schlüssel erkannt werden. Wenn ich sie auf dem Bildschirm ausgeben lasse und manuell hinter einen Listennamen setze, scheinen sie als aufeinanderfolgende Schlüssel zu funktionieren, aber im Programmablauf selbst werden sie offenbar als "Texteinheit" gesehen.

Gibt es einen Weg, die Schlüsseleinträge aus der Liste zu "aufzuarbeiten", daß sie als aufeinanderfolgende Schlüssel ausgewertet werden?
BlackJack

Das ist ein schlechter Witz, oder?
Benutzeravatar
nkoehring
User
Beiträge: 543
Registriert: Mittwoch 7. Februar 2007, 17:37
Wohnort: naehe Halle/Saale
Kontaktdaten:

Einen wunderschoenen guten Abend merlin_emrys,

also ich werde aus deinem Code gerade nicht so schlau und muss auch gestehen mich nicht sonderlich angestrengt zu haben, das zu aendern. Das hat nichts mit dem Code oder dir zu tun sondern schlicht und einfach mit meiner Muedigkeit :oops:

Aber ich will trotzdem gern helfen und frage deshalb: Was genau machst du denn da? Koenntest du mal zeigen, wie es denn aussehen soll oder so?

Eine Vermutung: Kann es sein, dass deine Liste ein String ist und du doch tatsaechlich versuchst mit diesen eckigen Klammern eine List daraus werden zu lassen? Wenn ja, solltest du dich dringenst mit den grundlegenden Dingen in Python auseinander setzen.

Eine Liste erweiterst du um einen Begriff, indem du die "append"-Methode nutzt:

Code: Alles auswählen

In [1]: liste = ["hallo", "welt"]

In [2]: liste
Out[2]: ['hallo', 'welt']

In [3]: liste = ["hallo"]

In [4]: liste
Out[4]: ['hallo']

In [5]: liste.append("welt")

In [6]: liste
Out[6]: ['hallo', 'welt']
[url=http://www.python-forum.de/post-86552.html]~ Wahnsinn ist auch nur eine andere Form der Intelligenz ~[/url]
hackerkey://v4sw6CYUShw5pr7Uck3ma3/4u7LNw2/3TXGm5l6+GSOarch/i2e6+t2b9GOen7g5RAPa2XsMr2
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

BlackJack hat geschrieben:Das ist ein schlechter Witz, oder?
Warum? Ich habe nie behauptet, ein begnadeter Programmierer zu sein. Ich behelfe mir halt, so gut ich kann.
Auch wenn Du es witzig findest - ich wäre an Hilfe interessiert, nicht an Spott.
nkoehring hat geschrieben:Was genau machst du denn da? Koenntest du mal zeigen, wie es denn aussehen soll oder so?

Eine Vermutung: Kann es sein, dass deine Liste ein String ist und du doch tatsaechlich versuchst mit diesen eckigen Klammern eine List daraus werden zu lassen? Wenn ja, solltest du dich dringenst mit den grundlegenden Dingen in Python auseinander setzen.
Meine Liste kommt zu Anfang aus einer Textdatei, ja. Und ich brauche die einzelnen Einträge als Schlüssel für ein ineinandergeschachteltes Dictionary. Der Umweg über eine Liste ergibt sich daraus, daß ich die mit "splitlines" ohnehin bekomme. Die anderen Versionen, die Begriffe zu bekommen, haben nicht geklappt (ich habe, wie erwähnt, nie behauptet, ein begnadeter Programmierer zu sein). Die grundlegensten Dinge von Python sind insofern kein wirklich hilfreicher Kommentar.

Auch den Hinweis auf append kann ich nicht einordnen. Damit die Listeneinträge als aufeinanderfolgende Schlüssel funktionieren, brauche ich sie ja jeweils als eigenes Element.
BlackJack

Mein Kommentar sollte sagen: Kein Mensch, ausser Dir wird verstehen was die Eingabedaten bedeuten und was dieser Code daraus macht. Das ist einfach nicht wirklich verständlich.

Es bringt oft mehr wenn man ein Problem beschreibt und nicht eine Lösung für das Problem ohne dasselbe zu erklären.

Der Hinweis auf die grundlegenden Datenstrukturen kam, weil Du Zeichenketten zusammen setzt, die (fast) aussehen wie Listen von Zeichenketten, inklusive der Anführungszeichen. Da die als Schlüssel verwendet werden, wären hier vielleicht Tupel als Datentyp für die Schlüssel eine "natürlichere" Wahl.

Da ich nicht verstehe was der Quelltext machen soll, bleibt nur ein Kommentar zu den Zeilenfortsetzungszeichen ('\'): Wenn ich das richtig sehe, sind die alle überflüssig. Solange noch Klammern ('(', '[', und '{') "offen" sind, kann man ohne den '\' in der nächsten Zeile weiter machen.
Benutzeravatar
nkoehring
User
Beiträge: 543
Registriert: Mittwoch 7. Februar 2007, 17:37
Wohnort: naehe Halle/Saale
Kontaktdaten:

merlin_emrys hat geschrieben:
BlackJack hat geschrieben:Das ist ein schlechter Witz, oder?
Warum? Ich habe nie behauptet, ein begnadeter Programmierer zu sein. Ich behelfe mir halt, so gut ich kann.
Auch wenn Du es witzig findest - ich wäre an Hilfe interessiert, nicht an Spott.
nkoehring hat geschrieben:Was genau machst du denn da? Koenntest du mal zeigen, wie es denn aussehen soll oder so?

Eine Vermutung: Kann es sein, dass deine Liste ein String ist und du doch tatsaechlich versuchst mit diesen eckigen Klammern eine List daraus werden zu lassen? Wenn ja, solltest du dich dringenst mit den grundlegenden Dingen in Python auseinander setzen.
Meine Liste kommt zu Anfang aus einer Textdatei, ja. Und ich brauche die einzelnen Einträge als Schlüssel für ein ineinandergeschachteltes Dictionary. Der Umweg über eine Liste ergibt sich daraus, daß ich die mit "splitlines" ohnehin bekomme. Die anderen Versionen, die Begriffe zu bekommen, haben nicht geklappt (ich habe, wie erwähnt, nie behauptet, ein begnadeter Programmierer zu sein). Die grundlegensten Dinge von Python sind insofern kein wirklich hilfreicher Kommentar.

Auch den Hinweis auf append kann ich nicht einordnen. Damit die Listeneinträge als aufeinanderfolgende Schlüssel funktionieren, brauche ich sie ja jeweils als eigenes Element.
Hmm... also ich versuchs nochmal, in der Hoffnung, dass du den guten Willen erkennst und ihn auch bei dir auspraegst!

Ich habe leider immer noch nicht verstehen koennen, was du ueberhaupt machen moechtest. Es wuerde mir weiterhelfen, wenn du versuchst genau zu beschreiben was passieren soll und wofuer das ganze getan wird - also die Absicht dahinter.

Wenn ich zB eine einfach Konfigurationsdatei auslesen moechte, mache ich das gern so:

Code: Alles auswählen

In [11]:# dies sei der Einfachheit halber mal der Inhalt der Konfigurationsdatei
# zB mit file(path, "r").read().strip() ausgelesen
s="""test=eins,zwei,drei
   ....: blub=hans,dieter,bernd"""

In [12]: s
Out[12]: 'test=eins,zwei,drei\nblub=hans,dieter,bernd'

In [13]: print s
test=eins,zwei,drei
blub=hans,dieter,bernd

In [14]: config = dict()

In [15]: for line in s.split("\n"):
   ....:     key, values = line.split("=")
   ....:     config[key] = values.split(",")
   ....:
   ....:

In [17]: config
Out[17]: {'blub': ['hans', 'dieter', 'bernd'], 'test': ['eins', 'zwei', 'drei']}
[url=http://www.python-forum.de/post-86552.html]~ Wahnsinn ist auch nur eine andere Form der Intelligenz ~[/url]
hackerkey://v4sw6CYUShw5pr7Uck3ma3/4u7LNw2/3TXGm5l6+GSOarch/i2e6+t2b9GOen7g5RAPa2XsMr2
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

BlackJack hat geschrieben:Mein Kommentar sollte sagen: Kein Mensch, ausser Dir wird verstehen was die Eingabedaten bedeuten und was dieser Code daraus macht. Das ist einfach nicht wirklich verständlich.
Ich bin nicht sicher, ob ich es in Worten verständlicher ausdrücken kann - mir fehlt hier übliches Fachvokabular, was dann ja auch wieder Verwirrung stiften kann.
nkoehring hat geschrieben: Hmm... also ich versuchs nochmal, in der Hoffnung, dass du den guten Willen erkennst und ihn auch bei dir auspraegst!
Der gute Wille ist schon da, er hilft nur nicht, weil ihm so gut wie keinerlei Fähigkeiten beistehen ... Immerhin hab ich geschafft, den Code bei pocoo unterzubringen, für Nicht-Fachleute ist das schon schwierig :-( . Ich habe das, was ich machen möchte, mit der "langen", zu ersetzenden Variante hinbekommen, und angenommen, es würde verdeutlichen, worauf ich hinausmöchte.

Aber nochmal in Langform:

Ich habe eine Textdatei, die Daten in der Form
- Hauptgruppe1
-- Teilgruppe11 [12]
-- Teilgruppe12 [75]
--- Untergruppe121 [2]
- Hauptgruppe2 [18]
enthält (im Code etwas länger, in den Zeilen 85 - 90).
Es gibt also drei "hierarchische Ebenen", und in einer Gruppe von Zeilen kann die hierarchische Ebene jeweils um eins zunehmen oder beliebig weit wieder abnehmen, bis auf die erste Ebene.
Was ich also schon mal nicht habe, sind die = zwischen den eingeklammerten Werten und den Begriffen, die ich als Schlüssel haben will :-( . Aber das würde ich vermutlich hinbekommen.

Mein Problem ist folgendes:
Ich habe Hauptgruppen, die Teilgruppen enthalten, die Untergruppen enthalten. Ich möchte daraus ein Dict machen, das die Hauptgruppen als "erste Ebene" enthält, alle gleichwertig nebeneinander jeweils als eigene Dictionaries. In diesen Dictionaries sollen sich dann die jeweiligen Teilgruppen als Dictionaries finden, und in den Teilgruppen-Dictionaries sollen die Untergruppen als Dictionaries liegen.

Der "auskommentierte" Code macht das auch. Aber BlackJack hat mich so oft für Codewiederholugen angepflaumt, daß ich dachte, es kann so noch nicht bleiben :-( . Man müsste es doch "verallgemeinern" können...

Was ich bisher mache, ist also folgendes:
Ich nehme die Textzeilen, die den gewünschten Datenbereich enthält, und zerlege ihn in einzelne Zeilen. Die stehen zu dem Zeitpunkt noch alle in einer Liste, beispielsweise "Gruppen_Zeilenliste".

Der Teil "dictaufbau" (Zeilen 0 bis 21, in Zeile 92 aufgerufen) geht die Zeilen durch und sieht zuerst nach, ob ein "-" vorkommt (Zeilen 4-6). (Kommentarzeilen z.B. beginnen nicht mit "-".) Wenn eine Zeile also Datenzeile ist, wird bestimmt, welche "hierarchische Ebene" der darin stehende Begriff (derzeit ersetzt durch die Begriffe "Hauptgruppe" usw.) hat.
Ist die hierarchische Ebene der nächsten Zeile höher als die der vorigen, soll ein Dictionary innerhalb des zuvor angelegten erstellt werden. Deshalb merkt sich die "Schluesselliste", welche Schluessel diese jeweils angeben, und der neue Begriff wird angehängt. (Das handeln die Zeilen 7 - 9 ab.)
Ist die hierarchische Ebene der beiden Zeilen gleich, wird von der Schluesselliste der letzte Schlüssel (der jetzt nicht mehr gebraucht wird, weil in diesem Teildictionary keine Eingaben mehr gemacht werden sollen) "abgehängt" und dafür der neue Schlüssel angehängt. (Zeilen 11 - 14.)
Sinkt die hierarchische Ebene, werden entsprechend viele Schlüsselteile "abgehängt". (Zeilen 16 - 20)

Für das "Anhängen" selbst ist der Teil "dict_erweitern" zuständig (Zeilen 23 - 81).
Er nimmt (in den Zeilen 26 - 31) zuerst den Teil, der dann zu "Wahrsch" wird, ab, wenn der überhaupt vorhanden ist. Mit Sicherheit kann ich nur sagen, daß der Ausdruck, den ich vom vorigen trennen möchte, wenn überhaupt ganz hinten steht und in eckige Klammern eingeschlossen ist. Ich verwende daher split mit dem Trennzeichen "[", weil der Begriff, der davorsteht, eventuell Leerzeichen enthalten kann. Ich kann also nicht einfach nach Worten splitten. Auch nach Zahlen zu suchen geht nicht, weil "Wahrsch" auch aus Buchstaben bestehende Platzhalter sein können. Deshalb bin ich auf die Klammer verfallen, weil sie eindeutig ist.
In der Liste "Begriffsteile" habe ich damit zwei Teile, einen Begriff und einen Wert.

Mit dem Begriff, wie er zu Anfang ohne Ergänzung war, oder dem ersten Teil von zweien mache ich dann weiter.
In der "alten Version", die in den Zeilen 49 - 76 steht, habe ich die Begriffe der Reihe nach aus der Schlüsselliste abgerufen und als Schlüssel hinter dem Dictionary-Namen eingetragen. Das hat auch funktioniert; ich habe damit meine geschachtelten Dictionaries bekommen, in die ich (wenn vorhanden) dann den abgetrennten "Warhsch"-Wert eingetragen habe; anderfalls habe ich ein leeres Dict erzeugt.
Aber wie bekomme ich die Schlüssel in der richtigen Reihenfolge hinter den Dictionary-Namen, wenn ich sie nicht einzeln abrufen kann? Die Liste selbst geht nicht, weil sie ja durch Kommata abgetrennte Elemente enthält.
Ich habe vermutet, daß es über eine Zeichenkette machbar sein müsste, die den richtigen Aufbau hat. Als das nicht ging, waren meine eigenen Ideen alle und ich habe mir gesagt, ich finde vielleicht Hilfe bei besseren Programmierern...


Edit
Alternativ (ich probier es aber erst, wenn ich geschlafen habe) fällt mir bei Deinem Beispiel, nkoehring, ein: Ich könnte zuerst nach der größten Zahl von "-" suchen (also der innersten hierarchischen Ebene) und die jeweiligen Einträge in Dicts verwandeln und dann an den String der jeweils übergelagerten Ebene anhängen... Aber so spontan kommt mir das nicht sonderlich "elegant" vor, weil ich erstens die Liste dann mehrfach durchgehen muß und zweitens die ganzen inneren Dicts als Zeichenkette "mitschleife". Aber wie bekomme ich sie an die Stelle in die anderen hinein, wenn ich sie einzeln erzeuge?
BlackJack

Lösungsvorschlag: http://paste.pocoo.org/show/20610/

Ich habe erst einmal das heraus ziehen der Informationen und filtern von Zeilen, die nicht dem Muster entsprechen, vom Aufbau des verschachtelten Dictionary getrennt. Funktionen sollten nicht zu viel auf einmal machen. Wenn man die Aufgaben so verteilt, dass jede Funktion nur eine begrenzte Aufgabe erfüllt, kann man den Quelltext besser testen. Programme sind so auch einfacher erweiter- und veränderbar. Wenn sich das Datenformat zum Beispiel ändern sollte, braucht man nur diese Funktion `parse_lines()` anpassen oder neu schreiben. Der Aufbau des Dictionary bleibt davon unberührt.

Die Zeilen werden erst einmal in eine Folge von Tupeln der Form (Ebene, Schlüssel, "Warsch"-Wert) umgewandelt:

Code: Alles auswählen

>>> lines = ('# comment', '- main group', '-- sub group [42]', 'garbage')
>>> list(parse_lines(lines))
[(1, 'main group', None), (2, 'sub group', '42')]
Daraus wird in `build_dict()` das Dictionary aufgebaut. Dabei kommt ein `collections.defaultdictionary` zum Einsatz, das seine eigene Klasse als Default-Wert hat. Damit wird das erzeugen der verschachtelten Datenstruktur ganz einfach, man braucht nur auf einen nicht existierenden Schlüssel zugreifen und bekommt eine neue Ebene in der Datenstruktur. Das ist schon fast Perl. 8-)
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

BlackJack hat geschrieben:Lösungsvorschlag: http://paste.pocoo.org/show/20610/
Ganz herzlichen Dank... Leider haben mit die fast 24 Stunden noch nicht gereicht, ihn zu verstehen :-o ...
Immerhin habe ich inzwischen herausgefunden, warum die "regular expressions" das machen, was sie tun :-o .
BlackJack hat geschrieben: Daraus wird in `build_dict()` das Dictionary aufgebaut. Dabei kommt ein `collections.defaultdictionary` zum Einsatz, das seine eigene Klasse als Default-Wert hat.
Vermutlich muß ich mich doch erst nach einem Python-Kurs umsehen... :-( Weder mithilfe der Python-Docs noch mit meinem Buch habe ich herausgefunden, was das bedeutet und wie es funktioniert... :-o (wenn es funktioniert, der Rechner, an dem ich es gestern testen wollte, hat behauptet, es gäbe in "collections" kein "defaultdict"...)

Ich werde mal weitergrübeln und mich wieder melden, wenn ich den Eindruck habe, ich wüßte wenigstens, was ich nicht verstehe... :-)

Vielen Dank nochmal!
BlackJack

Das `collections.defaultdict` gibts seit Python 2.5. Daran liegt's vielleicht. So einem `defaultdict` kann man im Konstruktor eine Funktion mitgeben, die aufgerufen wird, wenn auf einen Schlüssel zugegriffen wird, den es nicht gibt. Der Rückgabewert der Funktion ist dann der Rückgabewert des Dictionary-Zugriffs und Schlüssel plus Wert werden automatisch in das Dictionary eingetragen.

Code: Alles auswählen

In [232]: from collections import defaultdict

In [233]: a = defaultdict(int)

In [234]: a
Out[234]: defaultdict(<type 'int'>, {})

In [235]: a['answer'] = 42

In [236]: a
Out[236]: defaultdict(<type 'int'>, {'answer': 42})

In [237]: a['answer']
Out[237]: 42

In [238]: a['spam']
Out[238]: 0

In [239]: int()
Out[239]: 0
In 238 wird auf den nicht vorhandenen Schlüssel 'spam' zugegriffen und deshalb das Ergebnis vom Aufruf von `int()` zurück gegeben.

Bei dem `NestedDefaultDict`, was darauf aufbaut, ist die Frabrikfunktion eben wieder ein `NestedDefaultDict`-Objekt und wenn man auf einen beliebigen Schlüssel einfach nur zugreift, ensteht so schon ein verschachtelter Eintrag.

Code: Alles auswählen

In [240]: class NestedDefaultDict(defaultdict):
   .....:     def __init__(self):
   .....:         defaultdict.__init__(self, NestedDefaultDict)
   .....:

In [241]: a = NestedDefaultDict()

In [242]: a
Out[242]: defaultdict(<class '__main__.NestedDefaultDict'>, {})

In [243]: a['Hauptgruppe']
Out[243]: defaultdict(<class '__main__.NestedDefaultDict'>, {})

In [244]: a
Out[244]: defaultdict(<class '__main__.NestedDefaultDict'>, {'Hauptgruppe':
defaultdict(<class '__main__.NestedDefaultDict'>, {})})
Deshalb braucht man in `build_dict()` nur die drei Zeilen unter dem Kommentar 'Walk path.', um alle Ebenen, die durch den Pfad beschrieben werden, zu erzeugen, falls sie noch nicht existieren.

Ein paar kleine Änderungen und man kommt ohne das `defaultdict` aus: http://paste.pocoo.org/show/20844/
Antworten