Tabelle als Dict?

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
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Ich habe Daten in Form einer Tabelle vorliegen und muss damit arbeiten. Könnte vereinfacht so aussehen:

Code: Alles auswählen

 id | name | parent
 ---+------+--------
  1 | foo  | None
  2 | bar  | 1
Als einziges Äquivalent in Python fällt mir eine Kombination aus List und Dict ein, also z.B.:

Code: Alles auswählen

rows = []
rows.append({'id':1, 'name':"foo", 'parent':None})
rows.append({'id':2, 'name':"bar", 'parent':1})
Frage 1: Gibt's noch was besseres?

Dann muss ich eine Liste von Daten mit dieser Tabelle abgleichen. Ich habe beispielsweise diesen Datensatz:

Code: Alles auswählen

d = {'name':"bar", 'parent':1}
Ich muss nun checken, ob es in meiner Tabelle bereits einen Datensatz gibt, der name="bar" und parent=1 hat — ein Wert für id ist mir nicht bekannt. In SQL könnte ich mir nun die Abfrage SELECT * FROM table WHERE name='bar' AND parent=1 vorstellen, aber die Tabelle soll im Python-Programm als Variable vorliegen. Ich würde mir erstmal so behelfen:

Code: Alles auswählen

rows = []
rows.append({'id':1, 'name':"foo", 'parent':None})
rows.append({'id':2, 'name':"bar", 'parent':1})

d = {'name':"bar", 'parent':1}

found = False
for i in rows:
	if i['name'] == d['name'] and i['parent'] == d['parent']:
		found = True
		break
print found
Bei einer sehr großen Tabelle (10.000 Einträge und mehr) wird das zunehmend unperformant und zeitraubend.

Frage 2: Gibt's da nicht auch was besseres?
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Da du nicht geschrieben hast, warum die Tabelle direkt als Pythondatensatz verfügbar sein muss: Seit 2.5 ist SQLite standardmäßig in Python dabei. Wie wäre es damit? Damit kannst du sogar Tabellen nur im Speicher erstellen.

Ansonsten nimm keine Liste, sondern ein dict mit dem Namen als Key, um die dicts zu speichern:

Code: Alles auswählen

d = dict(foo=dict(name='foo', id=1, parent=None))
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Joghurt hat geschrieben:Da du nicht geschrieben hast, warum die Tabelle direkt als Pythondatensatz verfügbar sein muss: Seit 2.5 ist SQLite standardmäßig in Python dabei. Wie wäre es damit? Damit kannst du sogar Tabellen nur im Speicher erstellen.
Irgendwie hab ich schon auf "Nimm doch SQLite!" gewartet :twisted: Ich muss zugeben, Tabellen nur im Speicher zu erstellen klingt seeeeehr verlockend. Aber ich brauche das Ganze aus Kompatibilitätsgründen in Python 2.4, wenn nicht sogar 2.3. Außerdem möchte ich das dGanze so schlank und simpel wie möglich halten.
Joghurt hat geschrieben:Ansonsten nimm keine Liste, sondern ein dict mit dem Namen als Key, um die dicts zu speichern:

Code: Alles auswählen

d = dict(foo=dict(name='foo', id=1, parent=None))
Die Namen können aber mehrfach vorkommen. Daher scheidet diese Variante aus, oder? Welchen Vorteil bietet mir das verschachtelte Dict? Sind die Namen dort nicht doppelt gemoppelt und damit reduntant?
BlackJack

droptix hat geschrieben:Ich habe Daten in Form einer Tabelle vorliegen und muss damit arbeiten. Könnte vereinfacht so aussehen:

Code: Alles auswählen

 id | name | parent
 ---+------+--------
  1 | foo  | None
  2 | bar  | 1
Als einziges Äquivalent in Python fällt mir eine Kombination aus List und Dict ein, also z.B.:
Bei der Auswahl von Datenstrukturen muss man immer berücksichtigen was man mit den Daten später machen will, also in welcher Weise sie verarbeitet/"befragt" werden sollen. Alternativ liesse sich die Tabelle sicher als Baumstruktur darstellen.
Frage 1: Gibt's noch was besseres?

Dann muss ich eine Liste von Daten mit dieser Tabelle abgleichen. Ich habe beispielsweise diesen Datensatz:

Code: Alles auswählen

d = {'name':"bar", 'parent':1}
Ich muss nun checken, ob es in meiner Tabelle bereits einen Datensatz gibt, der name="bar" und parent=1 hat — ein Wert für id ist mir nicht bekannt. In SQL könnte ich mir nun die Abfrage SELECT * FROM table WHERE name='bar' AND parent=1 vorstellen, aber die Tabelle soll im Python-Programm als Variable vorliegen. Ich würde mir erstmal so behelfen:
Wenn das die Operation sein soll, die schnell läuft, dann kannst Du die Zeilen in ein Dictionary mit Name und Elternteil als Schlüssel stecken:

Code: Alles auswählen

def main():
    rows = [{'id': 1, 'name': 'foo', 'parent': None},
            {'id': 2, 'name': 'bar', 'parent': 1}]
    
    data = dict(((row['name'], row['parent']), row) for row in rows)
    
    test = {'name': 'bar', 'parent': 1}
    print (test['name'], test['parent']) in data
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Hui, sieht sehr elegant aus!

Was ich mit den Daten vorhabe: Ich durchsuche mein Dateisystem. Also sag ich meinem Python-Tool, welche Ordner es rekursiv durchwandern soll. Da mir os.walk() etwas zu stur und unflexibel ist, benötige ich eine eigene walk-Funktion.

Weil ich mehrere Ordner angeben kann, können sich Angaben auch überlagern (es wäre dumm, ist aber theoretisch möglich):

Code: Alles auswählen

seek = []
seek.append("/foo")
seek.append("/foo/bar")
Dasselbe gilt für Symlinks. Um erstens Zeit zu sparen und zweitens Infinite-Loops zu vermeiden (Thema Symlinks), halte ich bereits "besuchte" Dateien und Verzeichnisse in einer Liste fest. Während ich also rekursiv alle Verzeichnisse in seek durchsuche, muss ich bei jedem einzelnen prüfen, ob ich hier nicht schonmal gewesen bin. Falls ja, spare ich es mir, dasselbe Verzeichnis nochmal zu durchsuchen.

Die Prüfung kostet mich zwar Zeit, aber dafür kann ich auch sehr viel Zeit sparen. Später möchte ich auch angeben können, welche Verzeichnisse von der Suche ausgeschlossen werden sollen. Dafür benötige ich dann sowieso eine Prüfung... also wird sich diese Prüfung später sicher rentieren.

Weil sie aber Zeit kostet, möchte ich die Datenhaltung dabei so effektiv wie möglich halten. Ich hatte bereits erste Versuche, wobei ich jeweils den vollständigen Pfad in einer Liste gespeichert hatte. Bei sehr vielen Dateien werden die Werte der Liste extrem viel:

Code: Alles auswählen

visited = []
visited.append("/foo/file1)
visited.append("/foo/file2)
visited.append("/foo/file3)
visited.append("/foo/bar)
visited.append("/foo/bar/file4)
visited.append("/foo/bar/file5)
visited.append("/foo/bar/file6)
visited.append("/foo/bar/file7)
Letzlich möchte ich die besuchten Dateien und Verzeichnisse als Baum darstellen können, wie bereits richtig vermutet wurde. Daher halte ich eine Datenhaltung diesem Stil hier ganz sinnvoll:

Code: Alles auswählen

 id | parent | name
----+--------+------
  0 | None   | /
  1 | 0      | foo
  2 | 1      | file1
  3 | 1      | file2
  4 | 1      | file3
  5 | 1      | bar
  6 | 5      | file4
  7 | 5      | file5
  8 | 5      | file6
  9 | 5      | file7
Vielleicht möchte ich die später auch so in eine Datenbank eintragen oder als XML-Bericht exportieren.
Benutzeravatar
Blattlaus
User
Beiträge: 55
Registriert: Donnerstag 24. August 2006, 08:55

Wenn du es eh als Baum haben willst, wieso nimmst du dann nicht Objekte und verknüpfst sie untereinander. Imho ist es nicht besonders sinnvoll das alles in einer Tabelle zu verwalten, dass wird nur unübersichtlich.

Ensprechende Module sollte es fertig geben, aber selbst schreibe nginge natürlich auch.
BlackJack

droptix hat geschrieben:Was ich mit den Daten vorhabe: Ich durchsuche mein Dateisystem. Also sag ich meinem Python-Tool, welche Ordner es rekursiv durchwandern soll. Da mir os.walk() etwas zu stur und unflexibel ist, benötige ich eine eigene walk-Funktion.
Was ist Dir an `os.walk()` zu unflexibel?
Weil ich mehrere Ordner angeben kann, können sich Angaben auch überlagern (es wäre dumm, ist aber theoretisch möglich):

Code: Alles auswählen

seek = []
seek.append("/foo")
seek.append("/foo/bar")
Dasselbe gilt für Symlinks. Um erstens Zeit zu sparen und zweitens Infinite-Loops zu vermeiden (Thema Symlinks), halte ich bereits "besuchte" Dateien und Verzeichnisse in einer Liste fest. Während ich also rekursiv alle Verzeichnisse in seek durchsuche, muss ich bei jedem einzelnen prüfen, ob ich hier nicht schonmal gewesen bin. Falls ja, spare ich es mir, dasselbe Verzeichnis nochmal zu durchsuchen.
Dafür wäre ein `set()` wesentlich besser geeignet als eine Liste.
Weil sie aber Zeit kostet, möchte ich die Datenhaltung dabei so effektiv wie möglich halten. Ich hatte bereits erste Versuche, wobei ich jeweils den vollständigen Pfad in einer Liste gespeichert hatte. Bei sehr vielen Dateien werden die Werte der Liste extrem viel:
Das ist oft eine Entscheidung vor der man steht: Viel Speicherbedarf und schneller oder wenig Speicherbedarf und langsamer.
Letzlich möchte ich die besuchten Dateien und Verzeichnisse als Baum darstellen können, wie bereits richtig vermutet wurde. Daher halte ich eine Datenhaltung diesem Stil hier ganz sinnvoll:

Code: Alles auswählen

 id | parent | name
----+--------+------
  0 | None   | /
  1 | 0      | foo
  2 | 1      | file1
  3 | 1      | file2
  4 | 1      | file3
  5 | 1      | bar
  6 | 5      | file4
  7 | 5      | file5
  8 | 5      | file6
  9 | 5      | file7
Das ist aber eine Baumstruktur zu einer Liste/Tabelle "flachgeklopft". Das als Baum darzustellen macht mehr Arbeit als wenn man die Daten gleich in einer Baumstruktur zur Verfügung hätte.

Ausserdem bräuchtest Du noch eine Unterscheidung zwischen Dateien und Verzeichnissen. Sonst kannst Du ein leeres Verzeichnis nicht von einer Datei unterscheiden.

Und symbolische Links müsstest Du auch kennzeichnen, sonst hast Du unter Umständen einen Graphen und keinen einfachen Baum mehr.
Vielleicht möchte ich die später auch so in eine Datenbank eintragen oder als XML-Bericht exportieren.
Für die Datenbank bräuchte man eine "flache" Tabelle, die liesse sich mit einer rekursiven Funktion recht einfach aus einer Baumstruktur gewinnen und bei XML wäre eine Baumstruktur als Ausgangsbasis geeigneter. Es sei denn Du willst so eine Tabelle wie oben in XML modellieren, aber dann macht XML IMHO nicht so viel Sinn, weil eine CSV Datei die selbe Information einfacher darstellen könnte.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Ich behaupte jetzt mal, dass es dem Computer egal ist, ob die Tabelle übersichtlich ist.

Untereinander verknüpfte Objekte klingt erstmal recht verlockend, aber ich wollte es zunächst sehr simpel halten. Wenn jemand natürlich was feines fertiges kennt, bitte sehr: ich bin neugierig! Ich sehe darin momentan nur keinen echten Vorteil...

Die Tabelle oben ist die einfachste Möglichkeit, einen Baum zweidimensional darzustellen. Daher das Modell Tabelle. Ich nehme an, dass die Arbeit mit Objekten zum einen aufwändiger und zum anderen speicherintensiver ist, richtig?

Eine Unterscheidung für Dateien und Links wird natürlich getroffen... in zwei separaten Listen. Für das obige Beispiel:

Code: Alles auswählen

files = [2, 3, 4, 6, 7, 8, 9]
links = []
Für Dateien und Links werden später sowieso noch weitere Eigenschaften gespeichert.
Zuletzt geändert von droptix am Donnerstag 14. Dezember 2006, 16:58, insgesamt 3-mal geändert.
Benutzeravatar
Blattlaus
User
Beiträge: 55
Registriert: Donnerstag 24. August 2006, 08:55

droptix hat geschrieben:Ich behaupte jetzt mal, dass es dem Computer egal ist, ob die Tabelle übersichtlich ist.
Dem Computer vielleicht, aber du musst den Code ja schreiben und warten. Da ist mehr übersichtlichkeit und Logik imemr ganz toll ;)
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

BlackJack hat geschrieben:Was ist Dir an `os.walk()` zu unflexibel?
os.walk() wandert zum einen stur durch alle Verzeichnisse. Ich möchte aber wie oben bereits erwähnt vor dem rekursiven Durchwandern erstmal prüfen, ob ein (Unter)verzeichnis nicht vielleicht schon vorher besucht wurde.

Außerdem liefert mir os.walk Symlinks oder Mountpoints nicht separat, sondern ordnet diese als Verzeichnisse oder Dateien ein. Ich möchte das bereits vorher voneinander trennen, um speziell Symlinks gesondert zu behandeln.
Blattlaus hat geschrieben:Da ist mehr übersichtlichkeit und Logik imemr ganz toll
Logisch ist das Ganze ja... wunderbar sogar. Das Modell enthält imho auch keine redundanten Angaben. Es ist vielleicht nur nicht optimal beim Thema Übersichtlichkeit. Ich wage sogar zu behaupten, dass das Abarbeiten mit (ich brauche ingesamt 4) Listen schneller geht als wenn ich jede Datei und jedes Verzeichnis als Objekte darstelle, was allerdings auszuprobieren wäre 8)

Ich bin für jede gute Idee zu haben!
BlackJack

droptix hat geschrieben:Ich behaupte jetzt mal, dass es dem Computer egal ist, ob die Tabelle übersichtlich ist.
Wie Blattlaus schon sagte: Dem Computer ist es egal. Aber wenn Du mit einer unübersichtlichen Datenstrukturen arbeitest, dann wird auch der Quelltext undurchsichtig und das sollte Dir nicht egal sein. ;-)
Untereinander verknüpfte Objekte klingt erstmal recht verlockend, aber ich wollte es zunächst sehr simpel halten. Wenn jemand natürlich was feines fertiges kennt, bitte sehr: ich bin neugierig! Ich sehe darin momentan nur keinen echten Vorteil...
Der Vorteil ist, das Du es simpel halten kannst. Eine rekursive Funktion zur Ausgabe des Baums ist wesentlich kürzer und übersichtlicher als eine, die sich die Informationen erst mühsam aus einer Tabelle und mehreren Listen/Sets heraussuchen muss.
Die Tabelle oben ist die einfachste Möglichkeit, einen Baum zweidimensional darzustellen. Daher das Modell Tabelle. Ich nehme an, dass die Arbeit mit Objekten zum einen aufwändiger und zum anderen speicherintensiver ist, richtig?
Die Tabelle selbst ist vielleicht speicherschonender, aber wenn Du damit arbeitest, bekommst Du entweder langsamen Code oder Du musst dann doch wieder zusätzliche Datenstrukturen aufbauen, um mit den Informationen arbeiten zu können, die in einer Baumstruktur schon enthalten sind.
Eine Unterscheidung für Dateien und Links wird natürlich getroffen... in zwei separaten Listen. Für das obige Beispiel:

Code: Alles auswählen

files = [2, 3, 4, 6, 7, 8, 9]
links = []
Damit pflückst Du zusammengehörende Informationen immer mehr auseinander, anstatt sie sinnvoll in Objekten zusammenzufassen. Das führt zu aufwändigerem und undurchsichtigerem Quelltext, der viel mit Listen, Sets und Dictionaries und Indexen/Schlüsseln arbeitet.
Antworten