## Dictionaries ### Problem Um mehrere Listen zu verknüpfen, müssen wir jeweils den passenden Eintrag in einer Liste in der anderen suchen. Beispielsweise möchten wir die Postleitzahl zu den Schweizer Gemeinden finden, nur leider sind die Postleitzahlen in einer anderen Datei gespeichert (`plz.csv`). Wenn wir jedes Mal die ganze Liste durchsuchen müssen, dauert dies ziemlich lange. Geht das auch schneller? Wie wir wissen, können wir in einem (sortierten) Wörterbuch (Diktionär) effizient suchen: * Der Suchbereich wird fortlaufend halbiert (s.a. Binäre Suche in [[gf_informatik:suchen_und_sortieren_2023]]) * Bei $n$ Einträgen benötigt die Suche nach einem Element nur $log_2(n)$ Zugriffe, also * $10$ Zugriffe für $1024$ Elemente, * $20$ Zugriffe für $1024\cdot1024 \approx 1Mio$, * $30$ Zugriffe für $1024\cdot1024\cdot1024 \approx 1Mia$ ### Syntax Es wäre schön, hätten wir eine Datenstruktur, die das für uns erledigt... zum Glück gibt es sie: **Dictionary** (oft *dict* abgekürzt). * Ein Dictionary stellt eine Sammlung von `Schlüssel : Wert` Paaren dar (oder `key : value`). * Erlaubt direkten Zugriff auf jedes Element mit dem gewünschten *Schlüssel*. * In Wahrheit benützt sie nicht binäre Suche, sondern eine noch schnellere Zugriffsart: [[wpde>Hashtabelle]]. Ein leeres Dictionary wird mit geschweiften Klammern erzeugt: zip_codes = {} Alternativ können auch bereits Einträge von der Form `key : value` eingetragen werden: zip_codes = { 'Romanshorn' : 8590, 'Egnach' : 9322 } Auf ein bestimmtes Element kann über *eckige* Klammern und den Schlüssel (*key*) zugegriffen werden. Die Syntax ist die gleiche wie beim Listenzugriff, nur verwenden wir statt dem *Index* den *Schlüssel*. plz = zip_codes['Romanshorn'] print(plz) 8590 Ein neues Element kann mit derselben Syntax angelegt werden - ist der Schlüssel bereits vorhanden, wird der Wert überschrieben: zip_codes['Amriswil'] = 8580 # Legt ein neues Key-Value-Pair an. zip_codes['Romanshorn'] = 8591 # Überschreibt den bestehenden Eintrag für Romanshorn print(zip_codes) {'Romanshorn': 8591, 'Egnach': 9322, 'Amriswil': 8580} Mit dem `in` Schlüsselwort kann herausgefunden werden, ob ein Schlüssel im Dictionary vorhanden ist: print('Romanshorn' in zip_codes) True print('St. Gallen' in zip_codes) False Wir können eine `for`-Schleife über die Keys in einem Dictionary schreiben: for town in zip_codes: print(town) Romanshorn Egnach Amriswil Möchten wir in der Schleife sowohl Key als auch Value haben, verwenden wir die `items()` Funktion: for town, plz in zip_codes.items(): print(plz, town) 8591 Romanshorn 9322 Egnach 8580 Amriswil ### Aufgabe 1: Dictionary Syntax Schreibe Code wie die obigen Beispiele, um eine Sammlung von Postleitzahlen anzulegen. Wie müsste man vorgehen, wenn wir sowohl ein Mapping von Ortsnamen zu Postleitzahlen als auch umgekehrt von Postleitzahlen zu Ortsnamen haben möchten? ++++Antwort:| Wir benötigen dafür zwei separate Dictionaries. ++++ ### Aufgabe 2: CSV in ein Dictionary einlesen Lies die Datei [[https://kantonsschuleromanshorn.sharepoint.com/:f:/s/FSInformatik/Ek-Hi_stH2RMjDa-wQN9jekBMeF_YD6rvhmibDlNglGWxw?e=Y3AX65|plz.csv]] ein und erstelle daraus ein Dictionary von Ortsnamen zu Postleitzahl. Die Daten sehen so aus: PLZ,Ort,Kanton 1000,Lausanne,Waadt 1005,Lausanne,Waadt 1008,Prilly,Waadt 1009,Pully,Waadt ... Hinweise: * Die PLZ steht zuerst, also an `values[0]` * Der Ort ist an zweiter Stelle, also `values[1]` * Wir wollen ein **umgekehrtes** Dictionary von Ortsnamen zu PLZ. * Ein Ortsnamen kann mehrere Postleitzahlen haben - wir möchten immer die kleinste behalten (also `1000` für Lausanne, nicht `1005`). ++++Code| def load_zip_codes(): zip_codes = {} with open('plz.csv', 'r') as infile: for line in infile: values = line.split(',') try: plz = int(values[0]) town = values[1] if not town in zip_codes: zip_codes[town] = plz except ValueError: pass return zip_codes ++++ ### Aufgabe 3: Zwei Datasets kombinieren Kombiniere die Daten zu den Schweizer Gemeinden (aus [[gf_informatik:daten:processing#aufgabe_6|Data Processing A6]]) mit den Postleitzahl-Daten: Was ist PLZ der kleinsten Schweizer Gemeinde? Der flächenmässig grössten Gemeinde? ++++Antwort:| * 4535 Kammersrohr 32 Einwohner * 7550 Scuol 438.76 km² ++++ ++++Lösung:| smallest_town, smallest_pop = find_smallest_population() largest_town, largest_area = find_largest_area() zip_codes = load_zip_codes() print(zip_codes[smallest_town], smallest_town, smallest_pop, "Einwohner") print(zip_codes[largest_town], largest_town, largest_area, "km²") ++++ ### Mehr zu Dictionaries Du willst mehr über Dictionaries wissen und selber eines programmieren? [[dictionaries_tutorial|Schau hier]]! ### Nächstes Kapitel Weiter mit [[gf_informatik:daten:processing:maps]].