====== LU07.L09 - Datenstruktur begründet auswählen ====== ===== Die drei Fassungen ===== def mit_liste(ausleihen, gesperrte): out = [] for k in ausleihen: for konto, grund in gesperrte: if konto == k: out.append((k, grund)) break return out def mit_set(ausleihen, gesperrte): konten = {k for k, _ in gesperrte} gruende = dict(gesperrte) return [(k, gruende[k]) for k in ausleihen if k in konten] def mit_dict(ausleihen, gesperrte): index = dict(gesperrte) return [(k, index[k]) for k in ausleihen if k in index] assert mit_liste(ausleihen, gesperrte) == mit_set(ausleihen, gesperrte) == mit_dict(ausleihen, gesperrte) ===== Messung ===== 5000 Ausleihen, wachsende Sperrliste (Python 3.12, Werte eines Testlaufs): ^ Gesperrte Konten ^ Liste ^ Set + Dict ^ Dict ^ Indexaufbau ^ | 100 | 0.0057 s | 0.00026 s | 0.00028 s | 0.000004 s | | 2 000 | 0.1159 s | 0.00044 s | 0.00042 s | 0.000076 s | | 20 000 | 1.1453 s | 0.00282 s | 0.00142 s | 0.001014 s | Die Liste wird bei zehnfacher Datenmenge rund zehnmal langsamer (O(n) pro Abfrage, O(n·m) gesamt). Dict und Set bleiben nahezu konstant - der Anstieg dort stammt fast vollständig aus dem **Aufbau** des Index, nicht aus den Abfragen. ===== Antworten ===== **Ab wann lohnt sich der Index?** Schon bei 100 Einträgen ist die Dict-Fassung 20-mal schneller, und der Aufbau kostet vier Mikrosekunden. Die Faustregel lautet deshalb: Sobald in einer Schleife gesucht wird, lohnt sich der Index praktisch immer. Nicht lohnen tut er sich, wenn die Struktur für **eine einzige** Abfrage aufgebaut wird - dann kostet der Aufbau mehr, als die Suche spart. **Was geht verloren?** * Set und Dict verlieren die **Reihenfolge** der Originaldaten (Dict behält die Einfügereihenfolge, aber nicht die der Ausleihen). * **Duplikate** verschwinden. Steht ein Konto zweimal mit verschiedenen Gründen in der Liste, gewinnt beim Dict der letzte Eintrag - stillschweigend. * Die Schlüssel müssen **hashbar** sein. * Es wird zusätzlicher **Speicher** belegt. **Wann ist die Liste richtig?** * Wenn nur **einmal** gesucht wird. * Wenn die Sperrliste sehr klein ist und die Reihenfolge fachlich zählt (etwa «zuerst eingetragene Sperre zuerst anzeigen»). * Wenn Duplikate erhalten bleiben müssen, weil jedes eine eigene Sperre mit eigenem Datum ist. **Warum Dict statt Set?** Beide prüfen die Zugehörigkeit in O(1). Das Set braucht aber eine **zweite** Struktur für den Grund, also zwei Durchläufe über dieselben Daten und zwei Lookups pro Treffer. Das Dict liefert Prüfung und Grund in einer Struktur - sichtbar in der Messung bei 20'000 Einträgen (0.00142 s gegenüber 0.00282 s). **Empfehlung:** ''dict'', sobald mehr als eine Abfrage stattfindet. Das ''set'' ist die richtige Wahl, wenn Sie nur die Frage «ist enthalten?» beantworten und keinen Wert dazu brauchen. ===== Formulierungsbeispiel für das Portfolio (D1A) ===== > Für die Sperrprüfung habe ich die Liste durch ein Dict ersetzt. Gemessen mit 5000 Ausleihen gegen 20'000 gesperrte Konten: 1.145 s vorher, 0.0014 s nachher, bei identischem Ergebnis (per ''assert'' geprüft). Grund: Die Listenfassung durchsucht pro Ausleihe im Schnitt die halbe Sperrliste (O(n)), das Dict greift über den Hash direkt zu (O(1)). Der Preis ist zusätzlicher Speicher und der Verlust von Duplikaten - da eine Kontonummer nur einmal gesperrt sein kann, ist das hier unproblematisch. Wäre die Reihenfolge der Sperreinträge fachlich relevant, hätte ich stattdessen einen zusätzlichen Index neben der Liste gehalten. ---- {{tag>M323-LU07 M323-D1A}} [[https://creativecommons.org/licenses/by-nc-sa/4.0/ch/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] (c) Kevin Maurizi