Mengen¶
Einführung¶

Auch wenn die Mengenlehre über lange Zeit heftig kritisiert wurde und immer noch kritisiert wird, ist sie ein wesentliches Gebiet der Mathematik. Die heutige Mathematik ist in der Terminologie der Mengenlehre formuliert und baut auf deren Axiomen auf. Die Mengenlehre ist noch ein recht junges Gebiet der Mathematik. Der deutsche Mathematiker Georg Cantor (1845 - 1918) begründete die Mengenlehre mit seinem 1874 erschienenen Artikel "Über eine Eigenschaft des Inbegriffes aller reellen algebraischen Zahlen". Anfangs also bis ins Jahr 1877 bezeichnete er übrigens noch die Mengenlehre als "Mannigfaltigkeitslehre".
1895 gab er folgende Definition einer Menge: "Unter einer 'Menge' verstehen wir jede Zusammenfassung M von bestimmten wohlunterschiedenen Objekten m unserer Anschauung oder unseres Denkens (welche die 'Elemente' von M genannt werden) zu einem Ganzen."
Eine Menge kann beliebige Elemente enthalten: zum Beispiel Zahlen, Zeichen, Buchstaben, Wörter, Namen oder sogar andere Mengen. Eine Menge wird in der Mathematik üblicherweise mit einem Großbuchstaben bezeichnet.
Mengen in Python¶
Der Datentyp "set", der ein sogenannter "collection"-Typ ist, ist in Python seit Version 2.4. enthalten. Ein set enthält eine ungeordnete Sammlung von einmaligen und unveränderlichen Elementen. In anderen Worten: Ein Element kann in einem set-Objekt nicht mehrmals vorkommen, was bei Listen und Tupel jedoch möglich ist. Beim Datentyp set handelt es sich um die Python-Implementierung von Mengen, wie sie aus der Mathematik bekannt sind.
Sets erzeugen¶
Will man eine Menge erzeugen, so ist dies in Python3 sehr einfach. Man bedient sich der gewohnten mathematischen Schreibweise.
staedte = {'Hamburg', 'München', 'Frankfurt', 'Berlin'}
print(staedte)
'Berlin' in staedte
'Köln' in staedte
Wenn man sich das obige Beispiel genau anschaut, fällt auf, dass print die Elemente nicht notwendigerweise in der Reihenfolge der Definition ausgibt. Python-Sets sind ungeordnete Sammlungen: Sie speichern keine Elementpositionen und garantieren keine Einfügereihenfolge. Auf eine beobachtete Iterations- oder Ausgabereihenfolge sollte man sich deshalb nicht verlassen.
Set-Elemente müssen hashbar sein. Listen und Dictionaries sind beispielsweise nicht hashbar und können deshalb keine Set-Elemente sein. „Unveränderlich“ ist als Faustregel oft hilfreich, aber technisch ist Hashbarkeit die entscheidende Eigenschaft.
staedte = { ['Berlin', 3.42], ['Hamburg', 1.75], ['München', 1.41], ['Köln', 1.03], ['Frankfurt', 0.70], ['Stuttgart', 0.60]}
Mit Tupeln funktioniert es nur dann, wenn das jeweilige Tupel selbst hashbar ist. Ein Tupel, das beispielsweise eine Liste enthält, ist ebenfalls nicht hashbar.
staedte = {('Frankfurt', 0.7), ('Stuttgart', 0.6), ('Hamburg', 1.75), ('Berlin', 3.42), ('Köln', 1.03), ('München', 1.41)}
Neben Set-Literalen mit geschweiften Klammern gibt es den Konstruktor set(). Er akzeptiert ein beliebiges Iterable, nicht nur Sequenzen. So kann man etwa eine Liste oder einen String in eine Menge eindeutiger Elemente überführen.
Eine leere Menge schreibt man als set(). Die Schreibweise {} erzeugt dagegen ein leeres Dictionary.
liste_von_woertern = ["gut", "hilfreich", "besser", "optimal"]
menge_von_woertern = set(liste_von_woertern)
menge_von_woertern
Häufig wird die set-Funktion angewendet, um aus Listen oder anderen sequentiellen Datentypen mehrfache Vorkommen zu entfernen. Wir wollen dies in einem Beispiel mit einer Liste von Städten aus der Schweiz, Österreich und Deutschland zeigen. In dieser Liste erscheint die Stadt Zürich zweimal. In der erzeugten Menge erscheint sie wie erwartet nur noch einmal, da es in einer Menge keine mehrfachen Vorkommen geben kann:
liste_von_staedten = ["Frankfurt", "Zürich", "Bern", "Stuttgart", "Freiburg", "Ulm", "Hamburg", "München", "Nürnberg", "Zürich", "Bregenz", "Salzburg", "Wien"]
liste_von_staedten
menge_von_staedten = set(liste_von_staedten)
menge_von_staedten
Anschließend kann man dann wieder die Menge in eine Liste wandeln, falls man eine Liste als Datentyp braucht:
liste_von_staedten = list(menge_von_staedten)
liste_von_staedten
Wie wir sehen können, hat diese Vorgehensweise einen kleinen Schönheitsfehler. Mehrfache Vorkommen sind zwar nun nicht mehr in der Ergebnisliste enthalten, aber die ursprüngliche Reihenfolge ist in der Regel nicht mehr vorhanden.
Wir kommen nun zu einem weiteren Anwendungsfall der set-Funktion. Im folgenden Beispiel benutzen wir sie, um einen String in seine Zeichen zu vereinzeln:
x = set("Ein gutes Python-Tutorial")
x
type(x)
Mengen aus hashbaren Elementen¶
Die Elemente eines Sets müssen hashbar sein. Deshalb sind Listen als Elemente nicht erlaubt. Hashbare Tupel, Strings, Zahlen und frozenset-Objekte sind typische Beispiele zulässiger Set-Elemente.
Städte = set((("Python","Perl"), ("Paris", "Berlin", "London")))
Städte = set((["Python","Perl"], ["Paris", "Berlin", "London"]))
Frozensets¶
Ein set ist selbst veränderlich und deshalb nicht hashbar. Ein frozenset enthält ebenfalls eindeutige hashbare Elemente, kann nach seiner Erzeugung aber nicht mehr verändert werden. Dadurch ist ein frozenset selbst hashbar und kann beispielsweise als Dictionary-Schlüssel oder als Element eines anderen Sets dienen.
Städte = {"Frankfurt", "Basel","Freiburg"}
Städte.add("Straßburg")
Städte
Frozensets besitzen die nicht-verändernden Mengenoperationen wie Vereinigung, Schnitt oder Differenz, aber keine mutierenden Methoden wie add() oder remove():
Städte = frozenset(["Frankfurt", "Basel","Freiburg"])
Städte.add("Straßburg")
Farben = {"rot","grün"}
Farben.add("gelb")
Farben
Farben.add(["schwarz","weiß"])
Selbstverständlich wird ein Objekt nur dann als neues Element eingefügt, wenn es noch nicht enthalten ist. Ist es bereits enthalten, hat der Aufruf der Methode keine Auswirkungen.
clear-Methode¶
Alle Elemente einer Menge werden entfernt. Die Menge ist also anschließend leer, wie wir im folgenden Beispiel sehen:
cities = {"Stuttgart", "Konstanz", "Freiburg"}
cities.clear()
cities
copy-Methode¶
copy erzeugt eine flache Kopie einer Menge, die zurückgeliefert wird. Wir demonstrieren die Benutzung anhand eines Beispiels:
more_cities = {"Winterthur","Schaffhausen","St. Gallen"}
cities_backup = more_cities.copy()
more_cities.clear()
cities_backup
Nur für diejenigen, die glauben, dass eine einfache Zuweisung auch genügen könnte:
more_cities = {"Winterthur", "Schaffhausen", "St. Gallen"}
cities_backup = more_cities
more_cities.clear()
cities_backup
Die Zuweisung "cities_backup = more_cities" erzeugt nur einen Pointer, einen weiteren Namen für das gleiche Objekt.
difference-Methode¶
Diese Methode liefert die Differenz von zwei oder mehr Mengen zurück. Wir illustrieren dies wie immer an einem Beispiel:
x = {"a","b","c","d","e"}
y = {"b","c"}
z = {"c","d"}
x.difference(y)
x.difference(y).difference(z)
Statt die Methode "difference" zu benutzen, hätten wir auch den Operator "-" benutzen können:
x - y
x - y - z
difference_update-Methode¶
Die Methode "difference_update" entfernt alle Elemente einer anderen Menge aus einer Menge. "x.difference_update(y)" ist gleichbedeutend mit "x = x - y".
x = {"a","b","c","d","e"}
y = {"b","c"}
x.difference_update(y)
x
x = {"a","b","c","d","e"}
y = {"b","c"}
x = x - y
x
discard-Methode¶
Beim Aufruf von discard(el) wird das Element el aus einer Menge entfernt, falls es enthalten ist. Falls el nicht in der Menge enthalten ist, passiert nichts.
x = {"a","b","c","d","e"}
x.discard("a")
x
x.discard("z")
x
remove-Methode¶
Die Methode "remove" funktioniert wie discard(), aber falls el nicht in der Menge enthalten ist, wird ein Fehler generiert, d.h. ein KeyError:
x = {"a","b","c","d","e"}
x.remove("a")
x
x.remove("z")
x
union-Methode¶
Die Methode "union" liefert die Vereinigung von zwei Mengen als eine neue Menge zurück, d.h. alle Elemente, die in beiden Mengen vorkommen.
x = {"a","b","c","d","e"}
y = {"c","d","e","f","g"}
x.union(y)
Um die Vereinigen zu bilden kann man auch das Pipe-Zeichen "|" als Operator verwenden:
x = {"a","b","c","d","e"}
y = {"c","d","e","f","g"}
x | y
intersection-Methode¶
Mit der Methode intersection kann man die Schnittmenge von zwei Mengen bilden, wie wir im folgenden Beispiel sehen.
x = {"a","b","c","d","e"}
y = {"c","d","e","f","g"}
x.intersection(y)
Dies kann auch mit dem "&"-Zeichen formuliert werden:
x = {"a","b","c","d","e"}
y = {"c","d","e","f","g"}
x & y
isdisjoint-Methode¶
Diese Methode liefert True zurück, wenn zwei Mengen eine leere Schnittmenge haben.
x = {"a","b","c"}
y = {"c","d","e"}
x.isdisjoint(y)
x = {"a","b","c"}
y = {"d","e","f"}
x.isdisjoint(y)
issubset-Methode¶
x.issubset(y) liefert True zurück, falls x eine Untermenge von y ist. "<=" kann statt dem Aufruf der Methode verwendet werden. "<" prüft, ob es sich um eine echte Untermenge handelt: Wenn x < y gilt, dann enthält y mindestens ein Element, das nicht in x enthalten ist.
x = {"a","b","c","d","e"}
y = {"c","d"}
x.issubset(y)
y.issubset(x)
x < y
y < x # y ist eine echte Untermenge von x
x < x # eine Menge kann nie eine echte Untermenge ihrer selbst sein.
x <= x
issuperset-Methode¶
x.issuperset(y) liefert True zurück, falls x eine Obermenge von y ist. ">=" kann statt dem Aufruf der Methode verwendet werden. ">" prüft, ob es sich um eine echte Obermenge handelt: Wenn x > y gilt, dann enthält x mindestens ein Element, dass nicht in y enthalten ist.
x = {"a","b","c","d","e"}
y = {"c","d"}
x.issuperset(y)
x > y
x >= y
x >= x
x > x
x.issuperset(x)
pop¶
pop() liefert ein beliebiges Element der Menge zurück. Dieses Element wird dabei aus der Menge entfernt. Die Methode erzeugt einen KeyError, falls die Menge leer ist.
x = {"a","b","c","d","e"}
x.pop()
x.pop()
x.pop()
x.pop()
x.pop()
Auch wenn "pop" die Elemente in der Reihenfolge der internen Repräsentierung der Menge zurückliefert, so stimmt dennoch die Formulierung "pop() liefert ein beliebiges Element der Menge zurück." Die Menge die wir im "Kopf" haben, lautet ja im obigen Beispiel {"a","b","c","d","e"} und die Reihenfolge in der pop() die Elemente zurückliefert ist 'd', 'e', 'a', 'b', 'c'. Dass diese Reihenfolge für diese Menge nicht immer gleich ist, kann man in der folgenden neuen interaktiven Python-Sitzung sehen:
x = {"a", "b", "c", "d", "e", "f"}
x.pop()
x.pop()
x.pop()
x.pop()
x.pop()
Keine definierte Reihenfolge¶
Sets haben keine definierte Elementreihenfolge. Die konkrete Reihenfolge bei Iteration oder Darstellung ergibt sich aus Implementierungsdetails der Hash-Tabelle und kann sich beispielsweise durch einen anderen Programmlauf, eine andere Python-Version oder Änderungen an der Menge unterscheiden. Sie ist nicht zufällig im mathematischen Sinn, aber sie ist auch keine zugesicherte Programmschnittstelle.
Wenn eine stabile Reihenfolge benötigt wird, muss sie explizit hergestellt werden, beispielsweise mit sorted(menge) für gegenseitig vergleichbare Elemente.
x = {"a","b","c","d","e"}
x
staedte = {'Hamburg', 'München', 'Frankfurt', 'Berlin'}
x = {"a","b","c","d","e"}
x
staedte = ['Hamburg', 'München', 'Frankfurt', 'Berlin']
x = {"a","b","c","d","e"}
x

