17. Sets: Arbeiten mit eindeutigen, ungeordneten Daten
In früheren Kapiteln haben wir mit Listen (geordneten, veränderbaren Sammlungen) und Dictionaries (Key-Value-Zuordnungen) gearbeitet. Jetzt werden wir Sets erkunden, Pythons Sammlungstyp, der speziell dafür entwickelt wurde, eindeutige Elemente zu speichern und mathematische Mengenoperationen effizient auszuführen.
Sets sind besonders leistungsfähig, wenn Sie Duplikate entfernen, die Zugehörigkeit schnell testen oder Operationen wie das Finden gemeinsamer Elemente zwischen Sammlungen ausführen müssen. Anders als Listen sind Sets ungeordnet und können keine doppelten Werte enthalten—der Versuch, dasselbe Element zweimal hinzuzufügen, hat keine Wirkung.
17.1) Sets erstellen und grundlegende Operationen
17.1.1) Sets mit geschweiften Klammern erstellen
Die häufigste Methode, ein Set zu erstellen, ist die Verwendung von geschweiften Klammern {} mit durch Kommas getrennten Werten:
# Erstellen eines Sets von Programmiersprachen
languages = {"Python", "JavaScript", "Java", "C++"}
print(languages) # Output: {'Python', 'JavaScript', 'Java', 'C++'}
print(type(languages)) # Output: <class 'set'>Wichtig: Die Reihenfolge der Elemente beim Ausgeben eines Sets kann von der Reihenfolge abweichen, in der Sie sie eingegeben haben. Sets sind ungeordnete Sammlungen, was bedeutet, dass Python keine bestimmte Reihenfolge beibehält:
numbers = {5, 2, 8, 1, 9}
print(numbers) # Output might be: {1, 2, 5, 8, 9} or another orderDie Ausgabereihenfolge kann zwischen Python-Läufen und Versionen variieren. Verlassen Sie sich niemals darauf, dass Sets eine bestimmte Reihenfolge beibehalten—wenn die Reihenfolge wichtig ist, verwenden Sie stattdessen eine Liste.
17.1.2) Sets entfernen Duplikate automatisch
Eine der nützlichsten Eigenschaften von Sets ist, dass sie doppelte Werte automatisch eliminieren. Wenn Sie versuchen, ein Set mit doppelten Elementen zu erstellen, wird nur eine Kopie jedes eindeutigen Werts behalten:
# Erstellen eines Sets mit doppelten Werten
student_ids = {101, 102, 103, 102, 101, 104}
print(student_ids) # Output: {101, 102, 103, 104}
# Diese Eigenschaft macht Sets perfekt zum Entfernen von Duplikaten
grades = [85, 90, 85, 78, 90, 92, 78, 85]
unique_grades = set(grades)
print(unique_grades) # Output: {78, 85, 90, 92}Diese automatische Deduplizierung passiert, weil Sets ein mathematisches Mengenmodell verwenden, bei dem jedes Element nur einmal vorkommen darf. Wenn Sie einen Wert hinzufügen, der bereits existiert, ignoriert das Set das Duplikat einfach.
17.1.3) Sets mit dem set()-Konstruktor erstellen
Sie können Sets aus anderen Iterables mit dem set()-Konstruktor erstellen. Das ist besonders nützlich, um Listen, Tupel oder Strings in Sets umzuwandeln:
# Erstellen eines Sets aus einer Liste
colors_list = ["red", "blue", "green", "red", "yellow"]
colors_set = set(colors_list)
print(colors_set) # Output: {'red', 'blue', 'green', 'yellow'}
# Erstellen eines Sets aus einem String (jedes Zeichen wird zu einem Element)
letters = set("programming")
print(letters) # Output: {'p', 'r', 'o', 'g', 'a', 'm', 'i', 'n'}
# Erstellen eines Sets aus einem Tupel
coordinates = set((10, 20, 30, 20, 10))
print(coordinates) # Output: {10, 20, 30}Wenn Sie ein Set aus einem String erstellen, wird jedes eindeutige Zeichen zu einem separaten Element. Das ist nützlich, um alle unterschiedlichen Zeichen in einem Text zu finden:
text = "Mississippi"
unique_chars = set(text.lower())
print(unique_chars) # Output: {'m', 'i', 's', 'p'}
print(f"The word contains {len(unique_chars)} unique letters")
# Output: The word contains 4 unique letters17.1.4) Ein leeres Set erstellen
Hier ist ein kritischer Stolperstein: Sie können kein leeres Set mit {} erstellen, weil Python das als leeres Dictionary interpretiert. Stattdessen müssen Sie set() verwenden:
# FALSCH - Das erstellt ein leeres Dictionary, kein Set
empty_dict = {}
print(type(empty_dict)) # Output: <class 'dict'>
# RICHTIG - Das erstellt ein leeres Set
empty_set = set()
print(type(empty_set)) # Output: <class 'set'>
print(empty_set) # Output: set()Diese Unterscheidung existiert, weil Dictionaries vor Sets zu Python hinzugefügt wurden, sodass {} bereits für leere Dictionaries reserviert war. Wenn Sie ein leeres Set ausgeben, zeigt Python es als set() an, um Verwechslungen zu vermeiden.
Häufige Verwirrung bei Anfänger:innen: Wenn Sie ein Set mit einem einzelnen Element über eine Variable erstellen, enthält das Set den Wert der Variable, nicht den Variablennamen:
# Set-Erstellung mit Variablen verstehen
x = 5
my_set = {x} # Erstellt {5}, nicht {'x'}
print(my_set) # Output: {5}
# Wenn Sie ein Set mit dem String 'x' möchten:
my_set = {'x'}
print(my_set) # Output: {'x'}
# Das gilt für jeden Ausdruck
result = 10 + 5
my_set = {result} # Erstellt {15}
print(my_set) # Output: {15}17.1.5) Grundlegende Set-Eigenschaften und -Operationen
Sets unterstützen mehrere grundlegende Operationen, die sie für die Datenverarbeitung nützlich machen:
# Prüfen der Anzahl eindeutiger Elemente
website_visitors = {"alice", "bob", "charlie", "alice", "david"}
print(f"Unique visitors: {len(website_visitors)}")
# Output: Unique visitors: 4
# Zugehörigkeit mit 'in' prüfen (für Sets sehr schnell)
if "alice" in website_visitors:
print("Alice visited the website")
# Output: Alice visited the website
# Nicht-Zugehörigkeit prüfen
if "eve" not in website_visitors:
print("Eve has not visited yet")
# Output: Eve has not visited yetDas Prüfen der Zugehörigkeit (membership testing) mit in ist einer der zentralen Vorteile von Sets. Bei großen Sammlungen ist die Prüfung, ob ein Element in einem Set existiert, viel schneller als die Prüfung in einer Liste. Warum das wichtig ist, sehen wir in Abschnitt 17.5.
17.2) Elemente zu Sets hinzufügen und aus Sets entfernen
Anders als Tupel (die unveränderlich sind) sind Sets veränderlich—Sie können nach der Erstellung Elemente hinzufügen und entfernen. Allerdings müssen die Elemente selbst unveränderliche Typen sein (diese Einschränkung untersuchen wir in Abschnitt 17.7).
17.2.1) Einzelne Elemente mit add() hinzufügen
Das Hinzufügen einzelner Elemente zu einem Set ist mit der Methode add() unkompliziert. Wenn das Element bereits existiert, bleibt das Set unverändert—es wird kein Fehler ausgelöst und kein Duplikat erstellt:
# Aufbau eines Sets abgeschlossener Aufgaben
completed_tasks = {"task1", "task2"}
print(completed_tasks) # Output: {'task1', 'task2'}
# Hinzufügen einer neuen Aufgabe
completed_tasks.add("task3")
print(completed_tasks) # Output: {'task1', 'task2', 'task3'}
# Das Hinzufügen eines Duplikats hat keine Auswirkung
completed_tasks.add("task1")
print(completed_tasks) # Output: {'task1', 'task2', 'task3'}Dieses Verhalten macht Sets ideal, um eindeutige Vorkommen zu verfolgen. Sie können add() sicher aufrufen, ohne vorher zu prüfen, ob das Element bereits existiert—das Set behandelt Duplikate automatisch.
17.2.2) Mehrere Elemente mit update() hinzufügen
Um mehrere Elemente auf einmal hinzuzufügen, verwenden Sie update(), das jedes Iterable (Liste, Tupel, ein anderes Set usw.) akzeptiert und all dessen Elemente zum Set hinzufügt:
# Start mit einem kleinen Set an Fähigkeiten
skills = {"Python", "SQL"}
print(skills) # Output: {'Python', 'SQL'}
# Hinzufügen mehrerer Fähigkeiten aus einer Liste
new_skills = ["JavaScript", "Docker", "Python"]
skills.update(new_skills)
print(skills) # Output: {'Python', 'SQL', 'JavaScript', 'Docker'}Beachten Sie, dass "Python" sowohl im ursprünglichen Set als auch in der hinzuzufügenden Liste vorkam, das Set aber trotzdem nur eine Kopie enthält. Die Methode update() kann mehrere Iterables als Argumente annehmen:
# Kombinieren von Skills aus mehreren Quellen
current_skills = {"Python"}
course_skills = ["JavaScript", "HTML"]
job_requirements = {"SQL", "Python", "Docker"}
current_skills.update(course_skills, job_requirements)
print(current_skills)
# Output: {'Python', 'JavaScript', 'HTML', 'SQL', 'Docker'}17.2.3) Elemente mit remove() entfernen
Beim Entfernen von Elementen ist Vorsicht nötig. Die Methode remove() löscht ein Element aus einem Set, löst aber einen KeyError aus, wenn das Element nicht existiert:
# Verwalten aktiver Benutzer
active_users = {"alice", "bob", "charlie", "david"}
# Entfernen eines Benutzers, der sich abgemeldet hat
active_users.remove("bob")
print(active_users) # Output: {'alice', 'charlie', 'david'}
# Der Versuch, ein nicht existierendes Element zu entfernen, verursacht einen Fehler
# active_users.remove("eve") # Raises: KeyError: 'eve'Da remove() für fehlende Elemente einen Fehler auslöst, wird es am besten verwendet, wenn Sie sicher sind, dass das Element existiert, oder wenn Sie den Fehler abfangen möchten, falls es nicht existiert:
# Sicheres Entfernen mit Fehlerbehandlung (mehr über try/except lernen wir in Kapitel 28)
users = {"alice", "bob", "charlie"}
user_to_remove = "david"
if user_to_remove in users:
users.remove(user_to_remove)
print(f"Removed {user_to_remove}")
else:
print(f"{user_to_remove} was not in the set")
# Output: david was not in the set17.2.4) Elemente sicher mit discard() entfernen
Für sichereres Entfernen, das keine Fehler auslöst, bietet discard() eine tolerante Alternative. Es entfernt das Element, wenn es vorhanden ist, macht aber nichts, wenn das Element nicht existiert:
# Verwalten eines Warenkorbs
cart_items = {"apple", "banana", "orange"}
# Elemente sicher entfernen (kein Fehler, wenn das Element nicht existiert)
cart_items.discard("banana")
print(cart_items) # Output: {'apple', 'orange'}
cart_items.discard("grape") # No error, even though grape isn't in the set
print(cart_items) # Output: {'apple', 'orange'}Verwenden Sie discard(), wenn Sie sicherstellen möchten, dass ein Element nicht im Set ist, unabhängig davon, ob es anfangs vorhanden war. Verwenden Sie remove(), wenn das Fehlen des Elements einen Fehlerzustand anzeigt, den Sie erkennen möchten.
17.2.5) Ein beliebiges Element mit pop() entfernen und zurückgeben
Die Methode pop() entfernt und gibt ein beliebiges Element aus dem Set zurück. Da Sets ungeordnet sind, können Sie nicht vorhersagen, welches Element entfernt wird:
# Verarbeiten einer Warteschlange ausstehenden Aufgaben (Reihenfolge ist egal)
pending_tasks = {"email", "report", "meeting", "review"}
# Eine Aufgabe verarbeiten (es ist egal, welche)
task = pending_tasks.pop()
print(f"Processing: {task}") # Output: Processing: email (or another task)
print(f"Remaining: {pending_tasks}")
# Output: Remaining: {'report', 'meeting', 'review'} (without the popped task)Wenn Sie pop() bei einem leeren Set aufrufen, löst es einen KeyError aus:
empty_set = set()
# empty_set.pop() # Raises: KeyError: 'pop from an empty set'Die Methode pop() ist nützlich, wenn Sie alle Elemente eines Sets verarbeiten müssen, aber die Reihenfolge egal ist:
# Alle Elemente in einem Set verarbeiten
items_to_process = {"item1", "item2", "item3"}
while items_to_process:
item = items_to_process.pop()
print(f"Processing {item}")
# Verarbeiten Sie das Element...
print("All items processed")
# Output:
# Processing item1
# Processing item2
# Processing item3
# All items processed17.2.6) Alle Elemente mit clear() entfernen
Die Methode clear() entfernt alle Elemente aus einem Set und lässt es leer zurück:
# Zurücksetzen von Sitzungsdaten
session_data = {"user_id", "timestamp", "ip_address"}
print(session_data) # Output: {'user_id', 'timestamp', 'ip_address'}
session_data.clear()
print(session_data) # Output: set()
print(len(session_data)) # Output: 0Das ist effizienter, als ein neues leeres Set zu erstellen, wenn Sie dasselbe Set-Objekt wiederverwenden möchten.
17.3) Set-Operationen: Vereinigung, Schnittmenge, Differenz und symmetrische Differenz
Sets unterstützen mathematische Mengenoperationen, mit denen Sie Sammlungen effizient kombinieren, vergleichen und analysieren können. Diese Operationen sind grundlegend für die Mengenlehre und haben viele praktische Anwendungen in der Datenverarbeitung.
17.3.1) Vereinigung: Sets kombinieren
Beginnen wir mit einem praktischen Szenario, um zu verstehen, warum die Vereinigung wichtig ist. Stellen Sie sich vor, Sie verwalten Einschreibungen von Studierenden über verschiedene Kurse hinweg:
# Studierende, die in verschiedenen Kursen eingeschrieben sind
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
# Alle Studierenden finden, die einen der Kurse besuchen (oder beide)
all_students = python_students | javascript_students
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david', 'eve'}Die Vereinigung (union) zweier Sets enthält alle Elemente, die in einem der Sets (oder in beiden) vorkommen. Python bietet zwei Wege, Vereinigungen zu berechnen: den Operator | (oben gezeigt) und die Methode union():
# Dasselbe Ergebnis mit der Methode union()
all_students = python_students.union(javascript_students)
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david', 'eve'}Die Methode union() kann mehrere Sets als Argumente annehmen, was sie praktisch macht, um Daten aus vielen Quellen zu kombinieren:
# Studierende in drei verschiedenen Kursen
python_students = {"alice", "bob"}
javascript_students = {"bob", "charlie"}
sql_students = {"charlie", "david"}
# Alle Studierenden über alle Kurse hinweg
all_students = python_students.union(javascript_students, sql_students)
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david'}Ein weiteres Beispiel für Vereinigung ist das Kombinieren von E-Mail-Listen aus verschiedenen Abteilungen:
# Kombinieren von E-Mail-Listen aus verschiedenen Abteilungen
marketing_contacts = {"alice@company.com", "bob@company.com"}
sales_contacts = {"bob@company.com", "charlie@company.com"}
support_contacts = {"david@company.com", "alice@company.com"}
# Alle eindeutigen Kontakte über Abteilungen hinweg
all_contacts = marketing_contacts | sales_contacts | support_contacts
print(f"Total unique contacts: {len(all_contacts)}")
# Output: Total unique contacts: 417.3.2) Schnittmenge: Gemeinsame Elemente finden
Zu verstehen, welche Elemente in mehreren Sets vorkommen, ist für viele Datenanalyseaufgaben entscheidend. Die Operation Schnittmenge (intersection) beantwortet die Frage: „Was haben diese Sets gemeinsam?“
# Kunden finden, die beide Produkte gekauft haben
customers_product_a = {101, 102, 103, 104, 105}
customers_product_b = {103, 104, 105, 106, 107}
# Kunden, die beide Produkte gekauft haben
both_products = customers_product_a & customers_product_b
print(f"Bought both: {both_products}")
# Output: Bought both: {103, 104, 105}Die Schnittmenge enthält nur Elemente, die in beiden Sets vorkommen. Sie können auch die Methode intersection() verwenden, die mehrere Sets akzeptiert:
# Studierende finden, die in allen drei Kursen eingeschrieben sind
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "charlie", "david"}
sql_students = {"charlie", "eve", "bob"}
# Studierende, die alle drei Kurse belegen
all_three = python_students.intersection(javascript_students, sql_students)
print(all_three) # Output: {'bob', 'charlie'}Hier ist ein praktischer Anwendungsfall, um Produkte zu finden, die in mehreren Lagern verfügbar sind:
# Produkte finden, die in mehreren Lagern verfügbar sind
warehouse_a = {"laptop", "mouse", "keyboard", "monitor"}
warehouse_b = {"mouse", "keyboard", "printer", "scanner"}
warehouse_c = {"keyboard", "monitor", "mouse", "desk"}
# Produkte, die in allen Lagern verfügbar sind
available_everywhere = warehouse_a & warehouse_b & warehouse_c
print(f"Available in all locations: {available_everywhere}")
# Output: Available in all locations: {'mouse', 'keyboard'}17.3.3) Differenz: Elemente in einem Set, aber nicht in einem anderen
Manchmal müssen Sie identifizieren, was nur in einer Sammlung vorkommt. Die Operation Differenz (difference) findet Elemente, die im ersten Set, aber nicht im zweiten sind:
# Bestandsverwaltung: Abweichungen finden
expected_items = {"item001", "item002", "item003", "item004"}
actual_items = {"item001", "item003", "item005"}
# Fehlende Artikel im Bestand
missing = expected_items - actual_items
print(f"Missing items: {missing}")
# Output: Missing items: {'item002', 'item004'}
# Unerwartete Artikel im Bestand
unexpected = actual_items - expected_items
print(f"Unexpected items: {unexpected}")
# Output: Unexpected items: {'item005'}Sie können auch die Methode difference() verwenden:
# Studierende nur im Python-Kurs (nicht in JavaScript)
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
python_only = python_students.difference(javascript_students)
print(python_only) # Output: {'alice', 'charlie'}Wichtig: Die Differenz-Operation ist nicht kommutativ—die Reihenfolge ist entscheidend:
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
# Studierende in Python, aber nicht in JavaScript
python_only = python_students - javascript_students
print(f"Python only: {python_only}")
# Output: Python only: {'alice', 'charlie'}
# Studierende in JavaScript, aber nicht in Python
javascript_only = javascript_students - python_students
print(f"JavaScript only: {javascript_only}")
# Output: JavaScript only: {'david', 'eve'}17.3.4) Symmetrische Differenz: Elemente in einem der Sets, aber nicht in beiden
Die symmetrische Differenz (symmetric difference) findet Elemente, die in einem der Sets vorkommen, aber nicht in beiden. Diese Operation ist besonders nützlich, um Änderungen zwischen zwei Versionen zu identifizieren:
# Vergleich zweier Versionen einer Konfiguration
old_settings = {"debug", "logging", "cache", "compression"}
new_settings = {"logging", "cache", "monitoring", "security"}
# Einstellungen, die sich geändert haben (hinzugefügt oder entfernt)
changes = old_settings ^ new_settings
print(f"Changed settings: {changes}")
# Output: Changed settings: {'debug', 'compression', 'monitoring', 'security'}
# Um konkret zu sehen, was hinzugefügt vs. entfernt wurde:
removed = old_settings - new_settings
added = new_settings - old_settings
print(f"Removed: {removed}") # Output: Removed: {'debug', 'compression'}
print(f"Added: {added}") # Output: Added: {'monitoring', 'security'}Sie können auch die Methode symmetric_difference() verwenden:
# Studierende, die genau in einem Kurs sind (nicht in beiden)
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
one_course_only = python_students.symmetric_difference(javascript_students)
print(one_course_only)
# Output: {'alice', 'charlie', 'david', 'eve'}Anders als bei der Differenz ist die symmetrische Differenz kommutativ—die Reihenfolge spielt keine Rolle:
result1 = python_students ^ javascript_students
result2 = javascript_students ^ python_students
print(result1 == result2) # Output: True17.4) Teilmengen- und Obermengenbeziehungen (issubset, issuperset, isdisjoint)
Neben dem Kombinieren von Sets müssen wir oft die Beziehungen zwischen ihnen verstehen. Python bietet Methoden, um zu testen, ob ein Set in einem anderen enthalten ist, ein anderes enthält oder keine gemeinsamen Elemente mit einem anderen hat.
17.4.1) Teilmengen mit issubset() und <= testen
Ein Set A ist eine Teilmenge (subset) von Set B, wenn jedes Element in A auch in B enthalten ist. Anders ausgedrückt: B enthält alle Elemente von A (und möglicherweise mehr).
# Kursvoraussetzungen
basic_skills = {"reading", "writing"}
intermediate_skills = {"reading", "writing", "analysis"}
# Prüfen, ob basic_skills eine Teilmenge von intermediate_skills ist
print(basic_skills.issubset(intermediate_skills)) # Output: True
print(basic_skills <= intermediate_skills) # Output: True (same result)Ein Set ist immer eine Teilmenge von sich selbst:
skills = {"Python", "SQL", "JavaScript"}
print(skills.issubset(skills)) # Output: True
print(skills <= skills) # Output: TrueWenn Sie eine echte Teilmenge (proper subset) testen möchten (A ist Teilmenge von B, aber nicht gleich B), verwenden Sie den Operator <:
basic_skills = {"reading", "writing"}
intermediate_skills = {"reading", "writing", "analysis"}
# Echte Teilmenge: basic ist Teilmenge von intermediate UND sie sind nicht gleich
print(basic_skills < intermediate_skills) # Output: True
# Keine echte Teilmenge von sich selbst (sie sind gleich)
print(basic_skills < basic_skills) # Output: FalseEin praktisches Beispiel für Teilmengen-Tests ist das Prüfen von Berechtigungen oder Anforderungen:
# Benutzer-Berechtigungssystem
required_permissions = {"read", "write"}
user_permissions = {"read", "write", "delete", "admin"}
# Prüfen, ob der Benutzer alle benötigten Berechtigungen hat
if required_permissions.issubset(user_permissions):
print("Access granted")
else:
print("Access denied - missing permissions")
# Output: Access granted
# Ein weiterer Benutzer mit unzureichenden Berechtigungen
limited_user = {"read"}
if required_permissions.issubset(limited_user):
print("Access granted")
else:
missing = required_permissions - limited_user
print(f"Access denied - missing: {missing}")
# Output: Access denied - missing: {'write'}17.4.2) Obermengen mit issuperset() und >= testen
Ein Set A ist eine Obermenge (superset) von Set B, wenn A alle Elemente von B enthält. Das ist die umgekehrte Beziehung zur Teilmenge—wenn A eine Teilmenge von B ist, dann ist B eine Obermenge von A.
# Fähigkeitsstufen
basic_skills = {"reading", "writing"}
advanced_skills = {"reading", "writing", "analysis", "research"}
# Prüfen, ob advanced_skills eine Obermenge von basic_skills ist
print(advanced_skills.issuperset(basic_skills)) # Output: True
print(advanced_skills >= basic_skills) # Output: True (same result)Wie bei Teilmengen ist ein Set immer eine Obermenge von sich selbst:
skills = {"Python", "SQL"}
print(skills.issuperset(skills)) # Output: TrueFür eine echte Obermenge (proper superset) (A ist Obermenge von B, aber nicht gleich B) verwenden Sie den Operator >:
basic_skills = {"reading", "writing"}
advanced_skills = {"reading", "writing", "analysis"}
# Echte Obermenge: advanced enthält alles aus basic UND hat mehr
print(advanced_skills > basic_skills) # Output: True
# Keine echte Obermenge von sich selbst
print(advanced_skills > advanced_skills) # Output: False17.4.3) Auf disjunkte Sets mit isdisjoint() testen
Zwei Sets sind disjunkt (disjoint), wenn sie keine gemeinsamen Elemente haben—ihre Schnittmenge ist leer. Die Methode isdisjoint() gibt True zurück, wenn die Sets keine Elemente teilen:
# Auf Konflikte in der Terminplanung prüfen
morning_classes = {"math", "english", "history"}
afternoon_classes = {"science", "art", "music"}
# Prüfen, ob es Konflikte gibt (gleiche Klasse in beiden Sitzungen)
if morning_classes.isdisjoint(afternoon_classes):
print("No scheduling conflicts")
else:
conflicts = morning_classes & afternoon_classes
print(f"Conflicts: {conflicts}")
# Output: No scheduling conflictsWenn Sets nicht disjunkt sind:
morning_classes = {"math", "english", "history"}
afternoon_classes = {"science", "math", "music"}
if morning_classes.isdisjoint(afternoon_classes):
print("No scheduling conflicts")
else:
conflicts = morning_classes & afternoon_classes
print(f"Conflicts: {conflicts}")
# Output: Conflicts: {'math'}Leere Sets sind disjunkt zu allen Sets (einschließlich anderer leerer Sets):
empty = set()
numbers = {1, 2, 3}
print(empty.isdisjoint(numbers)) # Output: True
print(empty.isdisjoint(empty)) # Output: True17.5) Wann Sie Sets statt Listen verwenden sollten
Zu verstehen, wann Sie Sets gegenüber Listen verwenden sollten, ist entscheidend, um effizienten Python-Code zu schreiben. Während beide Sammlungen von Elementen speichern, haben sie unterschiedliche Eigenschaften, die jede für verschiedene Aufgaben geeignet machen.
17.5.1) Sets für schnelle Zugehörigkeitsprüfungen verwenden
Einer der wichtigsten Vorteile von Sets ist ihre Geschwindigkeit bei Zugehörigkeitsprüfungen. Zu prüfen, ob ein Element in einem Set existiert, ist viel schneller als in einer Liste, besonders bei großen Sammlungen:
# Prüfen, ob ein Benutzer in einer großen Sammlung ist
active_users_list = []
for i in range(10000):
active_users_list.append("user" + str(i))
# Mit einer Liste (langsam bei großen Sammlungen)
print("user5000" in active_users_list) # Checks each element until found
active_users_set = set()
for i in range(10000):
active_users_set.add("user" + str(i))
# Mit einem Set (schnell unabhängig von der Größe)
print("user5000" in active_users_set) # Direct lookupObwohl beide dasselbe Ergebnis liefern, ist die Set-Version bei großen Sammlungen dramatisch schneller. Das liegt daran, dass Sets intern eine Hash-Tabelle verwenden, die nahezu sofortige Lookups unabhängig von der Größe ermöglicht, während Listen jedes Element nacheinander prüfen müssen.
17.5.2) Sets verwenden, um Duplikate zu eliminieren
Wenn Sie Duplikate aus einer Sammlung entfernen müssen, ist die Umwandlung in ein Set der einfachste Ansatz:
# Doppelte Einträge aus Benutzereingaben entfernen
survey_responses = [
"yes", "no", "yes", "maybe", "yes", "no", "maybe", "yes"
]
# Eindeutige Antworten erhalten
unique_responses = set(survey_responses)
print(unique_responses) # Output: {'yes', 'no', 'maybe'}
# Wenn Sie wieder eine Liste benötigen (ohne Duplikate)
unique_list = list(unique_responses)
print(unique_list) # Output: ['yes', 'no', 'maybe'] (order may vary)17.5.3) Sets für mathematische Mengenoperationen verwenden
Wenn Sie gemeinsame Elemente, Unterschiede oder Vereinigungen zwischen Sammlungen finden müssen, bieten Sets klare, effiziente Operationen:
# Analyse von Kunden-Kaufmustern
customers_product_a = {101, 102, 103, 104, 105}
customers_product_b = {103, 104, 105, 106, 107}
# Kunden, die beide Produkte gekauft haben
both_products = customers_product_a & customers_product_b
print(f"Bought both: {both_products}")
# Output: Bought both: {103, 104, 105}
# Kunden, die nur Produkt A gekauft haben
only_a = customers_product_a - customers_product_b
print(f"Only product A: {only_a}")
# Output: Only product A: {101, 102}
# Alle Kunden, die mindestens ein Produkt gekauft haben
all_customers = customers_product_a | customers_product_b
print(f"Total customers: {len(all_customers)}")
# Output: Total customers: 717.5.4) Listen verwenden, wenn die Reihenfolge wichtig ist
Sets sind ungeordnet; wenn die Reihenfolge der Elemente wichtig ist, müssen Sie eine Liste verwenden:
# FALSCH - Reihenfolge wird mit Sets nicht beibehalten
task_order = {"wake up", "breakfast", "work", "lunch", "work", "dinner"}
print(task_order) # Order is unpredictable and "work" appears only once
# RICHTIG - Verwenden Sie eine Liste, wenn die Reihenfolge wichtig ist
task_order = ["wake up", "breakfast", "work", "lunch", "work", "dinner"]
print(task_order)
# Output: ['wake up', 'breakfast', 'work', 'lunch', 'work', 'dinner']17.5.5) Listen verwenden, wenn Duplikate bedeutungsvoll sind
Wenn doppelte Werte Informationen tragen (wie Häufigkeit oder mehrere Vorkommen), verwenden Sie eine Liste:
# Quiz-Ergebnisse aufzeichnen (Duplikate zeigen, wie viele Studierende jede Punktzahl erreicht haben)
quiz_scores = [85, 90, 85, 78, 90, 92, 85, 88]
# Mit einer Liste können wir Vorkommen zählen
score_85_count = quiz_scores.count(85)
print(f"Students who scored 85: {score_85_count}")
# Output: Students who scored 85: 3
# Mit einem Set würden wir diese Information verlieren
unique_scores = set(quiz_scores)
print(unique_scores) # Output: {78, 85, 88, 90, 92}
# Wir können nicht erkennen, wie viele Studierende jede Punktzahl erreicht haben17.5.6) Listen verwenden, wenn Sie Indexing benötigen
Sets unterstützen kein Indexing, weil sie ungeordnet sind. Wenn Sie Elemente nach Position abrufen müssen, verwenden Sie eine Liste:
# FALSCH - Sets unterstützen kein Indexing
colors = {"red", "blue", "green"}
# first_color = colors[0] # Raises: TypeError: 'set' object is not subscriptable
# RICHTIG - Verwenden Sie eine Liste für den Zugriff per Index
colors = ["red", "blue", "green"]
first_color = colors[0]
print(first_color) # Output: red17.6) Frozensets und unveränderliche Sets
Bisher haben wir mit normalen Sets gearbeitet, die veränderlich sind—Sie können nach der Erstellung Elemente hinzufügen und entfernen. Python bietet außerdem frozensets, die unveränderlichen Versionen von Sets. Einmal erstellt, kann ein frozenset nicht mehr verändert werden.
17.6.1) Frozensets erstellen
Sie erstellen ein frozenset mit dem Konstruktor frozenset(), ähnlich wie Sie ein normales Set mit set() erstellen:
# Erstellen eines frozenset aus einer Liste
colors = frozenset(["red", "blue", "green"])
print(colors) # Output: frozenset({'red', 'blue', 'green'})
print(type(colors)) # Output: <class 'frozenset'>
# Erstellen eines frozenset aus einem Tupel
numbers = frozenset((1, 2, 3, 4, 5))
print(numbers) # Output: frozenset({1, 2, 3, 4, 5})
# Erstellen eines leeren frozenset
empty = frozenset()
print(empty) # Output: frozenset()Wie normale Sets entfernen frozensets automatisch Duplikate:
# Duplikate werden entfernt
values = frozenset([1, 2, 2, 3, 3, 3, 4])
print(values) # Output: frozenset({1, 2, 3, 4})17.6.2) Frozensets sind unveränderlich
Einmal erstellt, können Sie ein frozenset nicht mehr verändern. Methoden wie add(), remove(), discard(), pop() und clear() existieren für frozensets nicht:
# Erstellen eines frozenset
languages = frozenset(["Python", "JavaScript", "Java"])
# Der Versuch, es zu verändern, löst einen Fehler aus
# languages.add("C++") # AttributeError: 'frozenset' object has no attribute 'add'
# languages.remove("Java") # AttributeError: 'frozenset' object has no attribute 'remove'Diese Unveränderlichkeit ist das definierende Merkmal von frozensets. Wenn Sie ein frozenset „verändern“ müssen, müssen Sie ein neues erstellen:
# Ursprüngliches frozenset
original = frozenset([1, 2, 3])
# Erstellen eines neuen frozenset mit einem zusätzlichen Element
modified = frozenset(list(original) + [4])
print(original) # Output: frozenset({1, 2, 3})
print(modified) # Output: frozenset({1, 2, 3, 4})17.6.3) Set-Operationen funktionieren mit Frozensets
Frozensets unterstützen alle gleichen Set-Operationen wie normale Sets (Vereinigung, Schnittmenge, Differenz usw.):
# Set-Operationen mit frozensets
set_a = frozenset([1, 2, 3, 4])
set_b = frozenset([3, 4, 5, 6])
# Vereinigung
print(set_a | set_b) # Output: frozenset({1, 2, 3, 4, 5, 6})
# Schnittmenge
print(set_a & set_b) # Output: frozenset({3, 4})
# Differenz
print(set_a - set_b) # Output: frozenset({1, 2})
# Symmetrische Differenz
print(set_a ^ set_b) # Output: frozenset({1, 2, 5, 6})Sie können in Operationen auch normale Sets und frozensets mischen:
regular_set = {1, 2, 3}
frozen_set = frozenset([3, 4, 5])
# Operationen zwischen normalen und eingefrorenen Sets
result = regular_set | frozen_set
print(result) # Output: {1, 2, 3, 4, 5}
print(type(result)) # Output: <class 'set'> (result is a regular set)17.6.4) Warum Frozensets verwenden?
Der Hauptgrund, frozensets zu verwenden, ist, dass sie als Dictionary-Keys oder als Elemente in anderen Sets verwendet werden können, was mit normalen Sets nicht möglich ist:
# FALSCH - Normale Sets können keine Dictionary-Keys sein
# regular_set = {1, 2, 3}
# my_dict = {regular_set: "value"} # TypeError: unhashable type: 'set'
# RICHTIG - Frozensets können Dictionary-Keys sein
frozen_set = frozenset([1, 2, 3])
my_dict = {frozen_set: "value"}
print(my_dict) # Output: {frozenset({1, 2, 3}): 'value'}
print(my_dict[frozen_set]) # Output: valueEin praktisches Beispiel, das frozensets als Dictionary-Keys verwendet:
# Informationen über Koordinatenpaare speichern
# Jede Koordinate ist ein frozenset aus (x, y)-Werten
location_data = {
frozenset([0, 0]): "origin",
frozenset([1, 0]): "east",
frozenset([1, 1]): "northeast"
}
# Einen Ort nachschlagen
point = frozenset([1, 0])
print(location_data[point]) # Output: eastFrozensets können auch Elemente in anderen Sets sein:
# FALSCH - Normale Sets können keine Elemente von Sets sein
# set_of_sets = {{1, 2}, {3, 4}} # TypeError: unhashable type: 'set'
# RICHTIG - Frozensets können Elemente von Sets sein
set_of_frozensets = {
frozenset([1, 2]),
frozenset([3, 4]),
frozenset([5, 6])
}
print(set_of_frozensets)
# Output: {frozenset({1, 2}), frozenset({3, 4}), frozenset({5, 6})}Ein praktisches Beispiel zur Darstellung von Gruppen:
# Teams darstellen, wobei jedes Team ein frozenset aus Spieler-IDs ist
tournament_teams = {
frozenset([101, 102, 103]), # Team A
frozenset([201, 202, 203]), # Team B
frozenset([301, 302, 303]) # Team C
}
# Prüfen, ob ein bestimmtes Team registriert ist
team_to_check = frozenset([101, 102, 103])
if team_to_check in tournament_teams:
print("Team is registered")
else:
print("Team not found")
# Output: Team is registered17.6.5) Zwischen Sets und Frozensets konvertieren
Sie können leicht zwischen normalen Sets und frozensets konvertieren:
# Ein normales Set in ein frozenset umwandeln
regular = {1, 2, 3, 4}
frozen = frozenset(regular)
print(frozen) # Output: frozenset({1, 2, 3, 4})
# Ein frozenset in ein normales Set umwandeln
frozen = frozenset([5, 6, 7, 8])
regular = set(frozen)
print(regular) # Output: {5, 6, 7, 8}
# Jetzt können wir das normale Set verändern
regular.add(9)
print(regular) # Output: {5, 6, 7, 8, 9}17.7) Hashable und Unhashable Types: Was kann Dictionary-Key oder Set-Element sein (und ein kurzer Hinweis zu Hashing)
In diesem Kapitel haben wir gesehen, dass Sets einige Objekttypen enthalten können, andere jedoch nicht. Zum Beispiel können Sie ein Set aus Integers oder Strings erstellen, aber kein Set aus Listen. Diese Einschränkung existiert, weil Set-Elemente (und Dictionary-Keys, wie wir in Kapitel 16 gelernt haben) hashable sein müssen.
17.7.1) Was bedeutet „Hashable“?
Ein hashable Objekt ist eines, das einen Hash-Wert hat, der sich während seiner gesamten Lebensdauer nie ändert. Python berechnet diesen Hash-Wert mit einer eingebauten Funktion namens hash():
# Hashable Typen haben einen Hash-Wert
print(hash(42)) # Output: 42
print(hash("Python")) # Output: (some large integer)
print(hash((1, 2, 3))) # Output: (some large integer)Der Hash-Wert ist ein Integer, den Python intern verwendet, um Objekte in Sets und Dictionaries schnell zu finden. Stellen Sie ihn sich wie eine Adresse oder einen Index vor, der Python hilft, Dinge effizient zu finden.
Schlüsseleigenschaft: Damit ein Objekt hashable ist, muss sein Hash-Wert während seiner gesamten Lebensdauer konstant bleiben. Das bedeutet, dass das Objekt selbst unveränderlich sein muss—wenn sich das Objekt ändern könnte, müsste sich auch sein Hash-Wert ändern, was Sets und Dictionaries kaputt machen würde.
17.7.2) Unveränderliche Typen sind hashable
Alle unveränderlichen eingebauten Typen von Python sind hashable und können als Set-Elemente oder Dictionary-Keys verwendet werden:
# Integers sind hashable
numbers = {1, 2, 3, 4, 5}
print(numbers) # Output: {1, 2, 3, 4, 5}
# Strings sind hashable
words = {"apple", "banana", "cherry"}
print(words) # Output: {'apple', 'banana', 'cherry'}
# Tupel sind hashable (wenn sie nur hashable Elemente enthalten)
coordinates = {(0, 0), (1, 1), (2, 2)}
print(coordinates) # Output: {(0, 0), (1, 1), (2, 2)}
# Frozensets sind hashable
frozen_sets = {frozenset([1, 2]), frozenset([3, 4])}
print(frozen_sets) # Output: {frozenset({1, 2}), frozenset({3, 4})}
# Booleans und None sind hashable
mixed = {True, False, None, 42, "text"}
print(mixed) # Output: {False, True, None, 42, 'text'}17.7.3) Veränderliche Typen sind nicht hashable
Veränderliche Typen wie Listen, normale Sets und Dictionaries sind nicht hashable, weil sich ihr Inhalt ändern kann:
# Listen sind NICHT hashable
# my_set = {[1, 2, 3]} # TypeError: unhashable type: 'list'
# Normale Sets sind NICHT hashable
# set_of_sets = {{1, 2}, {3, 4}} # TypeError: unhashable type: 'set'
# Dictionaries sind NICHT hashable
# my_set = {{"key": "value"}} # TypeError: unhashable type: 'dict'Warum ist Veränderlichkeit wichtig? Überlegen Sie, was passieren würde, wenn wir eine Liste zu einem Set hinzufügen könnten:
# Hypothetisches Szenario (das funktioniert tatsächlich nicht)
# my_list = [1, 2, 3]
# my_set = {my_list} # Angenommen, das würde funktionieren
#
# # Python berechnet den Hash basierend auf [1, 2, 3]
# # Jetzt verändern wir die Liste:
# my_list.append(4) # Jetzt ist es [1, 2, 3, 4]
#
# # Der Hash-Wert wäre falsch! Das Set wäre beschädigt.Deshalb verhindert Python, dass veränderliche Objekte in Sets vorkommen oder als Dictionary-Keys verwendet werden—es würde die interne Datenstruktur zerstören.
Häufige Verwirrung bei Anfänger:innen: Obwohl Sets selbst veränderlich sind (Sie können Elemente hinzufügen und entfernen), müssen die Elemente unveränderlich sein. Anfänger:innen versuchen manchmal, Objekte zu verändern, nachdem sie sie zu Sets hinzugefügt haben, ohne diese konzeptionelle Unterscheidung zu verstehen:
# Häufige Verwirrung: Das Set ist veränderlich, aber die Elemente müssen unveränderlich sein
# Das Set ist veränderlich - Sie können seinen Inhalt ändern
fruits = {'apple', 'banana'}
fruits.add('orange') # ✓ Works
fruits.remove('apple') # ✓ Works
# Aber Elemente müssen unveränderlich sein - sie können nicht geändert werden
my_list = [1, 2, 3]
# my_set = {my_list} # ✗ TypeError: unhashable type: 'list'
# Warum? Wenn Sie my_list nach dem Hinzufügen verändern könnten, wäre die interne
# Struktur des Sets beschädigt.
# Das funktioniert, weil Tupel unveränderlich sind
my_tuple = (1, 2, 3)
my_set = {my_tuple} # ✓ Works - tuples can't be modified17.7.4) Der Sonderfall von Tupeln
Tupel sind nur dann hashable, wenn alle ihre Elemente hashable sind. Ein Tupel, das veränderliche Objekte enthält, ist nicht hashable:
# Tupel nur mit unveränderlichen Elementen - hashable
good_tuple = (1, 2, "three")
my_set = {good_tuple} # Works: good_tuple is hashable
print(my_set) # Output: {(1, 2, 'three')}
# Tupel, das eine Liste enthält - NICHT hashable
bad_tuple = (1, 2, [3, 4])
# my_set = {bad_tuple} # TypeError: unhashable type: 'list'Das ist logisch: Auch wenn das Tupel selbst unveränderlich ist (Sie können nicht ändern, welche Objekte es enthält), kann sich der gesamte „Wert“ des Tupels ändern, wenn eines dieser Objekte veränderlich ist:
# Zeigt, warum Tupel mit veränderlichen Elementen nicht gehasht werden können
inner_list = [1, 2]
my_tuple = (inner_list, 3)
# Die Tupelstruktur ist fest, aber die Liste darin kann sich ändern
inner_list.append(3) # Jetzt ist inner_list [1, 2, 3]
# Das Tupel enthält jetzt „andere“ Daten, aber es ist dasselbe Tupel-Objekt17.7.5) Hashability testen
Sie können testen, ob ein Objekt hashable ist, indem Sie versuchen, seinen Hash zu berechnen:
# Hashability testen
def is_hashable(obj):
"""Prüfen, ob ein Objekt hashable ist."""
try:
hash(obj)
return True
except TypeError:
return False
# Verschiedene Typen testen
print(is_hashable(42)) # Output: True
print(is_hashable("text")) # Output: True
print(is_hashable((1, 2, 3))) # Output: True
print(is_hashable([1, 2, 3])) # Output: False
print(is_hashable({1, 2, 3})) # Output: False
print(is_hashable({"key": "value"})) # Output: False17.7.6) Zusammenfassung der hashable Typen
Hashable (können Set-Elemente oder dict keys sein):
- Integers:
42 - Floats:
3.14 - Strings:
"text" - Tupel (wenn alle Elemente hashable sind):
(1, 2, "three") - Frozensets:
frozenset([1, 2, 3]) - Booleans:
True,False - None:
None
Nicht hashable (können keine Set-Elemente oder dict keys sein):
- Listen:
[1, 2, 3] - Normale Sets:
{1, 2, 3} - Dictionaries:
{"key": "value"} - Tupel mit nicht hashable Elementen:
(1, [2, 3])
Das Verständnis von Hashability hilft Ihnen, die richtigen Datenstrukturen zu wählen und häufige Fehler beim Arbeiten mit Sets und Dictionaries zu vermeiden. Das Schlüsselprinzip ist einfach: Wenn sich ein Objekt ändern kann, kann es nicht gehasht werden; wenn es nicht gehasht werden kann, kann es nicht in einem Set sein oder als Dictionary-Key verwendet werden.