Zum Inhalt
wannacode

dict und set

Kurz gesagt: Eine Map (Dictionary) speichert Paare aus Schlüssel und Wert mit eindeutigen Schlüsseln und findet einen Wert schnell über seinen Schlüssel, und ein Set speichert eindeutige Werte.

Problem

Wer ist dieser Benutzer?

Die Benutzer der Website stehen in einer Liste. Bei jeder Anmeldung suchst du die E-Mail zum Benutzernamen:

def find_email(users, username):
for u in users:
if u.username == username:
return u.email
return None

Bei drei Benutzern geht das sofort. Bei 2 000 000 vergleicht jede Anmeldung bis zu 2 000 000 Namen, und es gibt Tausende Anmeldungen pro Minute. Ein Telefonbuch liest du auch nicht ab der ersten Seite. Du schlägst direkt beim Buchstaben auf.

Die Idee

Map: Schlüssel → Wert

Du willst fragen „Welche E-Mail hat weber?“ und sofort die Antwort bekommen. Die Collection dafür heißt Map (auch Wörterbuch oder Dictionary). Sie speichert Paare: einen Schlüssel und einen Wert. In Python ist sie eingebaut und heißt dict, kurz für Dictionary:

emails = {}
emails["mueller"] = "mueller@example.com"
emails["mschmidt"] = "mschmidt@example.com"
emails["weber"] = "weber@example.com"
print(emails["weber"])
print(emails.get("niemand"))
print("Benutzer:", len(emails))
Ausgabe
weber@example.com
None
Benutzer: 3
  • {} ist ein leeres Dictionary. emails[key] = value legt ein Paar hinein, und emails[key] gibt den Wert zurück.
  • get(key) gibt auch den Wert zurück, und für einen Schlüssel, den es nicht gibt, ergibt es None.
  • len(emails) ist die Anzahl der Paare. Du brauchst kein import.
  • Typen schreibst du nicht: In einem Dictionary können beliebige Schlüssel und Werte stehen. Wenn du sagen willst, welche es sind, gibt es einen Typhinweis: emails: dict[str, str] = {}.

emails[key] für einen Schlüssel, den es nicht gibt, ergibt nicht None, sondern bricht das Programm ab. Du kannst ein Dictionary auch auf einmal schreiben, mit Paaren key: value:

emails = {"mueller": "mueller@example.com", "mschmidt": "mschmidt@example.com"}
print(emails["mueller"])
try:
print(emails["niemand"])
except KeyError as e:
print(f"KeyError: {e}")
Ausgabe
mueller@example.com
KeyError: 'niemand'

emails[key] durchläuft nicht alle Paare. Es findet den Platz des Schlüssels fast sofort, egal wie viele es sind. Wie das geht, zeigt die nächste Lektion.

Die Idee

Das Lager: ändern und prüfen

Der Bestand im Lager: Artikel → Anzahl.

stock = {}
stock["Laptop"] = 4
stock["Maus"] = 25
stock["Laptop"] = 3
print(stock["Laptop"])
print("Monitor" in stock)
print(stock.get("Monitor", 0))
del stock["Maus"]
print(len(stock))
Ausgabe
3
False
0
1
  • Schlüssel sind eindeutig. Das zweite stock["Laptop"] = 3 fügt keine neue Zeile hinzu. Es ersetzt 4 durch 3.
  • key in stock fragt, ob der Schlüssel im Dictionary ist.
  • get(key, 0) gibt den Wert zurück, oder 0, wenn es den Schlüssel nicht gibt. Das schützt dich vor KeyError.
  • del stock[key] entfernt das Paar.
Genauer, für Neugierige
  • Werte dürfen sich wiederholen: Laptop und Maus können beide 3 sein. Schlüssel nicht.
  • Du kannst auch 3 in stock.values() fragen. Das durchläuft aber alle Werte, denn ein Dictionary ist nur nach Schlüssel schnell.

Probier es aus

Wie viele stehen auf der Karte?

Was gibt das Programm aus?

menu = {}
menu["Kaffee"] = 2
menu["Tee"] = 2
menu["Kaffee"] = 3
print(len(menu), menu["Kaffee"])
Antwort

Die Idee

Durchlaufen und Reihenfolge

Die Lieferkosten nach Stadt. items() liefert die Paare aus Schlüssel und Wert, und for city, price in teilt jedes in zwei Namen auf:

delivery = {}
delivery["Berlin"] = 4.5
delivery["Hamburg"] = 5.2
delivery["Bremen"] = 6.0
delivery["Dresden"] = 6.0
delivery["Leipzig"] = 5.8
for city, price in delivery.items():
print(f"{city}: {price:.2f} €")
Ausgabe
Berlin: 4.50 €
Hamburg: 5.20 €
Bremen: 6.00 €
Dresden: 6.00 €
Leipzig: 5.80 €

Die Städte kommen in der Reihenfolge heraus, in der sie hinzugefügt wurden. dict behält die Reihenfolge des Hinzufügens, und seit Python 3.7 ist das versprochen. :.2f gibt den Preis mit zwei Stellen nach dem Punkt aus. Mit denselben Zuweisungen und print(delivery):

delivery = {}
delivery["Berlin"] = 4.5
delivery["Hamburg"] = 5.2
delivery["Bremen"] = 6.0
delivery["Dresden"] = 6.0
delivery["Leipzig"] = 5.8
print(delivery)
Ausgabe
{'Berlin': 4.5, 'Hamburg': 5.2, 'Bremen': 6.0, 'Dresden': 6.0, 'Leipzig': 5.8}

Wenn du alphabetische Reihenfolge brauchst, gibt sorted(delivery) eine sortierte Liste der Schlüssel. Das Dictionary ändert sich nicht:

delivery = {}
delivery["Berlin"] = 4.5
delivery["Hamburg"] = 5.2
delivery["Bremen"] = 6.0
delivery["Dresden"] = 6.0
delivery["Leipzig"] = 5.8
for city in sorted(delivery):
print(f"{city}: {delivery[city]:.2f} €")
Ausgabe
Berlin: 4.50 €
Bremen: 6.00 €
Dresden: 6.00 €
Hamburg: 5.20 €
Leipzig: 5.80 €
Genauer, für Neugierige
  • Wenn du nur die Schlüssel brauchst, durchlaufe delivery. Wenn du nur die Werte brauchst, durchlaufe values():
for city in delivery:
print(city, end=" ")
print()
print(f"Durchschnitt: {sum(delivery.values()) / len(delivery):.2f} €")
Ausgabe
Berlin Hamburg Bremen Dresden Leipzig
Durchschnitt: 5.50 €
  • sum(...) addiert die Zahlen, und len(delivery) ist die Anzahl der Paare.
  • Das Dictionary behält die Reihenfolge des Hinzufügens, das set vom Ende der Lektion aber nicht.

Die Idee

Zählen

Wie oft wurde jeder Artikel bestellt? Der Schlüssel ist der Artikel, der Wert ist die Anzahl. Bei jeder Bestellung nimmst du die alte Anzahl (oder 0) und addierst 1:

orders = "Kaffee Tee Kaffee Cola Kaffee Tee"
counts = {}
for item in orders.split(" "):
counts[item] = counts.get(item, 0) + 1
print(counts)
Ausgabe
{'Kaffee': 3, 'Tee': 2, 'Cola': 1}

Das ist die häufigste Verwendung einer Map: Wörter zählen, Stimmen, Fehler nach Art, Verkäufe nach Stadt. Die Artikel kommen in der Reihenfolge heraus, in der sie zum ersten Mal vorkamen.

orders = "Kaffee Tee Kaffee Cola Kaffee Tee"
counts = {}
for item in orders.split(" "):
counts[item] = counts.get(item, 0) + 1
print(counts)
Schritt für Schritt, in Worten
  1. 6 Bestellungen und ein leeres Dictionary.
  2. „Kaffee“ ist noch nicht da: get(item, 0) gibt 0, und die Zuweisung fügt den Schlüssel mit 1 hinzu.
  3. „Tee“ ist noch nicht da: get(item, 0) gibt 0, und die Zuweisung fügt den Schlüssel mit 1 hinzu. Das Dictionary behält die Reihenfolge des Hinzufügens: Der neue Schlüssel kommt ans Ende.
  4. „Kaffee“ ist schon da: get gibt 1, und die Zuweisung ersetzt den Wert durch 2.
  5. „Cola“ ist noch nicht da: get(item, 0) gibt 0, und die Zuweisung fügt den Schlüssel mit 1 hinzu. Das Dictionary behält die Reihenfolge des Hinzufügens: Der neue Schlüssel kommt ans Ende.
  6. „Kaffee“ ist schon da: get gibt 2, und die Zuweisung ersetzt den Wert durch 3.
  7. „Tee“ ist schon da: get gibt 1, und die Zuweisung ersetzt den Wert durch 2.
  8. Jeder Artikel ist nur einmal ein Schlüssel, und der Wert sagt, wie oft er bestellt wurde.
Genauer, für Neugierige
  • split(" ") aus der Lektion über Strings teilt die Zeile in Wörter.
  • Zählen ist so häufig, dass es in collections ein fertiges Dictionary dafür gibt, Counter. Für einen Schlüssel, den es nicht gibt, ergibt es 0, nicht KeyError:
from collections import Counter
orders = "Kaffee Tee Kaffee Cola Kaffee Tee"
counts = Counter(orders.split(" "))
print(counts)
print(counts["Kaffee"], counts["Saft"])
Ausgabe
Counter({'Kaffee': 3, 'Tee': 2, 'Cola': 1})
3 0

Probier es aus

Finde den Fehler

Das Programm soll die Bestellungen zählen. Stattdessen bricht es schon bei der ersten ab:
Fehler
KeyError: 'Kaffee'

In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

set: jedes nur einmal

Wie viele verschiedene Leute haben sich heute auf der Website angemeldet? Wer sich fünfmal anmeldet, soll nur einmal zählen. Eine Collection, die jeden Wert nur einmal speichert, heißt Set (Menge):

visits = ["anna@example.com", "jonas@example.com", "anna@example.com", "maria@example.com", "jonas@example.com"]
visitors = set()
for email in visits:
visitors.add(email)
print("Besuche:", len(visits))
print("Verschiedene Besucher:", len(visitors))
print("maria@example.com" in visitors)
Ausgabe
Besuche: 5
Verschiedene Besucher: 3
True
  • set() ist ein leeres Set. {} ist keins: Das ist ein leeres Dictionary.
  • add auf einen Wert, der schon darin ist, macht nichts. In beiden Fällen gibt es None zurück, darum sagt es nicht, ob es etwas hinzugefügt hat:
seen = set()
print(seen.add("F-101"))
Ausgabe
None
  • value in visitors ist so schnell wie in bei einem Dictionary. Innen ist ein Set wie ein Dictionary, in dem du nur die Schlüssel speicherst.
  • Es gibt keine Indizes. Ein Set beantwortet nur die Frage „Ist es hier drin?“.
Genauer, für Neugierige
  • Ein Set behält keine Reihenfolge. Bei Text ändert sich die Reihenfolge sogar zwischen zwei Läufen desselben Programms, weil Python den Hash von Text bei jedem Lauf anders berechnet. Darum wird es mit sorted() ausgegeben. Du kannst ein Set auch auf einmal schreiben, in {} mit Werten:
tags = {"java", "sql", "git", "java"}
print(len(tags))
print(sorted(tags))
backend = {"java", "sql", "docker"}
print(sorted(tags & backend))
print(sorted(tags | backend))
Ausgabe
3
['git', 'java', 'sql']
['java', 'sql']
['docker', 'git', 'java', 'sql']
  • & behält nur die Elemente, die zwei Sets gemeinsam haben, und | vereinigt sie. Du brauchst sie in der Übung.

Probier es aus

Eine doppelte Rechnung

Die Buchhalterin will die Rechnungsnummern sehen, die zweimal eingegeben wurden. Was fragt, ob die Nummer schon im Set ist?
invoices = ["F-101", "F-102", "F-101", "F-103"]
seen = set()
for number in invoices:
    if number  seen:
        print("Doppelte Rechnung:", number)
    seen.add(number)

Übung

Maps im Einsatz

Löse die Aufgaben in deinem Editor.

1. Eine Benutzertabelle. Eine Klasse UserTable mit den Methoden add(username, email), get(username) und size(). Innen ist sie ein Dictionary. Füge mueller, mschmidt und weber mit E-Mails der Form mueller@example.com hinzu und suche mschmidt und niemand. In der nächsten Lektion baust du auch deine eigene Hashtabelle, ohne dict.

Ausgabe
mschmidt: mschmidt@example.com
niemand: unbekannter Benutzer
Benutzer: 3

2. Worthäufigkeit. Lies eine Zeile Text ein, schreib sie klein und zähle jedes Wort. Gib die Wörter in alphabetischer Reihenfolge aus.

Eingabe
Kaffee mit Milch und Kaffee ohne Zucker und Kaffee
Ausgabe
kaffee: 3
milch: 1
mit: 1
ohne: 1
und: 2
zucker: 1

3. Dokumente nach Stichwort. Eine Klasse DocumentIndex mit den Methoden add_document(doc_id, *keywords) und search(keyword), die eine Liste mit den Nummern der Dokumente mit diesem Wort zurückgibt. Innen ist sie ein Dictionary aus Listen: für jedes Wort eine Liste von Dokumenten. Füge Dokument 1 mit „OOP“ und „Java“ hinzu, Dokument 2 mit „SQL“ und „Datenbanken“ und Dokument 3 mit „Java“ und „Collections“. Suche nach „Java“, „SQL“ und „Python“.

Ausgabe
Java: [1, 3]
SQL: [2]
Python: []

4. Ein Kontaktnetz. Eine Klasse Contact mit Name und E-Mail und eine Klasse ContactNetwork mit den Methoden add_contact(name, email), add_connection(a, b) (die Verbindung gilt in beide Richtungen), friends_of(name), common(a, b) (gemeinsame Freunde) und email_of(name). Speichere die Kontakte in einem Dictionary Name → Contact und die Verbindungen in einem Dictionary Name → Set von Namen. friends_of und common geben eine sortierte Liste zurück. Verbinde Jonas mit Maria und Jonas mit Peter.

Ausgabe
Jonas: ['Maria', 'Peter']
Maria: ['Jonas']
Gemeinsame Freunde von Maria und Peter: ['Jonas']
E-Mail von Peter: peter@example.com

Weitere Aufgaben

  • Eine eindeutige ID. Eine Funktion generate_user_id(email) gibt jeder neuen E-Mail eine Nummer, U001, U002 und so weiter, und für eine schon gesehene E-Mail gibt sie dieselbe Nummer zurück. Groß- und Kleinbuchstaben in der E-Mail gelten als gleich. Eine Variante mit einer Hashfunktion gibt es in der nächsten Lektion.
  • Ein Buchkatalog. Der Schlüssel ist die ISBN, zum Beispiel 978-3-16-148410-0, und der Wert ist der Titel. Füge Bücher hinzu, suche eines und gib alle aus, sortiert nach ISBN.
  • Ein Reiseplaner. Speichere für jedes Reiseziel eine Liste von Aktivitäten: „Paris“ → „der Eiffelturm“, „der Louvre“. Gib den Plan in der Reihenfolge aus, in der die Reiseziele hinzugefügt wurden. Brauchst du mehr als ein einfaches Dictionary?
  • Ein Online-Wörterbuch. Wort → Erklärung. Lies Wörter ein, bis „ende“ kommt. Für ein unbekanntes Wort gib „Dieses Wort gibt es nicht.“ aus.
  • Noten nach Fach. Lies Zeilen wie „Mathe 6“, „Physik 5“, „Mathe 4“ ein und gib die Durchschnittsnote für jedes Fach aus, sortiert nach Namen.

Test

Teste dich

1. Wann ist ein Dictionary (dict) besser als eine Liste?
2. Im Dictionary menu gibt es schon den Schlüssel "Kaffee" mit dem Wert 2. Was macht menu["Kaffee"] = 3?
3. Den Schlüssel "Tee" gibt es in menu nicht. Was machen menu["Tee"] und menu.get("Tee")?
4. Du gibst ein Dictionary aus. In welcher Reihenfolge kommen die Schlüssel heraus?
5. Was gibt add auf einem set zurück, wenn der Wert schon darin ist?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·