Mengen

Einführung

Grafische Notation von Mengen in Kreisdiagrammen

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)
{'Hamburg', 'Frankfurt', 'München', 'Berlin'}
'Berlin' in staedte
Ausgabe:
True
'Köln' in staedte
Ausgabe:
False

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]}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-67-a4cdbbefd7dd> in <module>
----> 1 staedte = { ['Berlin', 3.42], ['Hamburg', 1.75],  ['München', 1.41], ['Köln', 1.03], ['Frankfurt', 0.70], ['Stuttgart', 0.60]}

TypeError: unhashable type: 'list'

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
Ausgabe:
{'besser', 'gut', 'hilfreich', 'optimal'}

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
Ausgabe:
['Frankfurt',
 'Zürich',
 'Bern',
 'Stuttgart',
 'Freiburg',
 'Ulm',
 'Hamburg',
 'München',
 'Nürnberg',
 'Zürich',
 'Bregenz',
 'Salzburg',
 'Wien']
menge_von_staedten = set(liste_von_staedten)
menge_von_staedten
Ausgabe:
{'Bern',
 'Bregenz',
 'Frankfurt',
 'Freiburg',
 'Hamburg',
 'München',
 'Nürnberg',
 'Salzburg',
 'Stuttgart',
 'Ulm',
 'Wien',
 'Zürich'}

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
Ausgabe:
['Hamburg',
 'Frankfurt',
 'München',
 'Wien',
 'Ulm',
 'Salzburg',
 'Bern',
 'Stuttgart',
 'Bregenz',
 'Freiburg',
 'Nürnberg',
 'Zürich']

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
Ausgabe:
{' ',
 '-',
 'E',
 'P',
 'T',
 'a',
 'e',
 'g',
 'h',
 'i',
 'l',
 'n',
 'o',
 'r',
 's',
 't',
 'u',
 'y'}
type(x)
Ausgabe:
set

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"]))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-59-0e894acf2c44> in <module>
      1 Städte = set((("Python","Perl"), ("Paris", "Berlin", "London")))
----> 2 Städte = set((["Python","Perl"], ["Paris", "Berlin", "London"]))

TypeError: unhashable type: 'list'

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")
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-57-f55e3ae660bc> in <module>
      1 Städte = frozenset(["Frankfurt", "Basel","Freiburg"])
----> 2 Städte.add("Straßburg")

AttributeError: 'frozenset' object has no attribute 'add'

Operationen auf set-Objekten

add-Methode

Mit add() fügt man ein hashbares Objekt in ein Set ein, falls es noch nicht vorhanden ist. Ein erneutes Hinzufügen desselben Wertes verändert das Set nicht.

Farben = {"rot","grün"}
Farben.add("gelb")
Farben
Ausgabe:
{'gelb', 'grün', 'rot'}
Farben.add(["schwarz","weiß"])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-56-7098b094f288> in <module>
----> 1 Farben.add(["schwarz","weiß"])

TypeError: unhashable type: 'list'

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
Ausgabe:
set()

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
Ausgabe:
{'Schaffhausen', 'St. Gallen', 'Winterthur'}

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
Ausgabe:
set()

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)
Ausgabe:
{'a', 'd', 'e'}
x.difference(y).difference(z)
Ausgabe:
{'a', 'e'}

Statt die Methode "difference" zu benutzen, hätten wir auch den Operator "-" benutzen können:

x - y
Ausgabe:
{'a', 'd', 'e'}
x - y - z
Ausgabe:
{'a', 'e'}

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
Ausgabe:
{'a', 'd', 'e'}
x = {"a","b","c","d","e"}
y = {"b","c"}
x = x - y
x
Ausgabe:
{'a', 'd', 'e'}

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
Ausgabe:
{'b', 'c', 'd', 'e'}
x.discard("z")
x
Ausgabe:
{'b', 'c', 'd', 'e'}

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
Ausgabe:
{'b', 'c', 'd', 'e'}
x.remove("z")
x
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-42-c666360df5af> in <module>
----> 1 x.remove("z")
      2 x

KeyError: 'z'

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)
Ausgabe:
{'a', 'b', 'c', 'd', 'e', 'f', 'g'}

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
Ausgabe:
{'a', 'b', 'c', 'd', 'e', 'f', 'g'}

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)  
Ausgabe:
{'c', 'd', 'e'}

Dies kann auch mit dem "&"-Zeichen formuliert werden:

x = {"a","b","c","d","e"}
y = {"c","d","e","f","g"}
x & y
Ausgabe:
{'c', 'd', 'e'}

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)
Ausgabe:
False
x = {"a","b","c"}
y = {"d","e","f"}
x.isdisjoint(y)
Ausgabe:
True

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)
Ausgabe:
False
y.issubset(x)
Ausgabe:
True
x < y
Ausgabe:
False
y < x # y ist eine echte Untermenge von x
Ausgabe:
True
x < x # eine Menge kann nie eine echte Untermenge ihrer selbst sein.
Ausgabe:
False
x <= x
Ausgabe:
True

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)
Ausgabe:
True
x > y
Ausgabe:
True
x >= y
Ausgabe:
True
x >= x
Ausgabe:
True
x > x
Ausgabe:
False
x.issuperset(x)
Ausgabe:
True

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()
Ausgabe:
'd'
x.pop()
Ausgabe:
'e'
x.pop()
Ausgabe:
'a'
x.pop()
Ausgabe:
'b'
x.pop()
Ausgabe:
'c'

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()
Ausgabe:
'f'
x.pop()
Ausgabe:
'd'
x.pop()
Ausgabe:
'e'
x.pop()
Ausgabe:
'a'
x.pop()
Ausgabe:
'b'

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
Ausgabe:
{'a', 'b', 'c', 'd', 'e'}
staedte = {'Hamburg', 'München', 'Frankfurt', 'Berlin'}
x = {"a","b","c","d","e"}
x
Ausgabe:
{'a', 'b', 'c', 'd', 'e'}
staedte = ['Hamburg', 'München', 'Frankfurt', 'Berlin']
x = {"a","b","c","d","e"}
x
Ausgabe:
{'a', 'b', 'c', 'd', 'e'}