leer = list()
leer2 = []

"""
Explanation: Level 3
In diesem Level lernen wir neue Datentypen, wie list, tuple, dict, set und frozenset kennen und lernen über Objekte dieser Typen mittels einer for-Schleife zu iterieren. Wir werden die Schlüsselwörter del und for kennenlernen und auch den Schlüsselwörtern in, break, continue und else ein weiteres Mal begegnen.
Einstieg
Bisher können wir Werte in Variablen speichern, das funktioniert auch, solange wir wissen, wieviele Werte wir speichern müssen. Das muss aber nicht der Fall sein. Die Datentypen, die wir in diesem Level kennenlernen ermöglichen es meherere Werte in einem Objekt zu speichern. Jeder dieser Typen hat dabei seine Besonderheiten, die wir im Laufe des Levels lernen werden.
list
Der list Typ ist der erste, den wir uns in diesem Level anschauen möchten. Er bietet viele Möglichkeiten an, ist einfach zu bedienen, weshalb er auch häufig benutzt wird. Im Gegensatz zu allen anderen Typen, die wir bereits kennengelernt haben und auch einigen anderen, die wir noch kennenlernen, ist die Liste ein veränderlicher Typ. Das heißt wir können ein list Objekt ändern und müssen es nicht überschreiben. Die Elemente eines list Objektes können einen beliebigen Typen haben.
Erstellen einer Liste
Zuerst erstellen wir eine leere Liste, entweder benutzen wir dafür die list() Funktion oder eckige Klammern [], welche die Literale einer Liste darstellen.
End of explanation
"""


liste = [0, True, 4.2, "foo"]
liste2 = list("abracadabra")
print(liste, "Länge:", len(liste))
print(liste2, "Länge:", len(liste2))

"""
Explanation: Es ist allerdings möglich eine Liste zu erstellen, die bereits Einträge enthält. Diese Einträge nennen wir Elemente. Für das erstellen können wir wieder die Literale nehmen oder die list() Funktion. Beim Benutzen der list() Funktion müssen wir allerdings beachten, dass diese ein Objekt in eine Liste umwandelt und das nicht mit allen Objekten geht. In dem Beispiel unten erstellen wir aus einem string eine Liste.
End of explanation
"""


liste = [0, True, 4.2, "foo"]
liste2 = list("abracadabra")
print(liste + liste2)
print(liste * 2)

"""
Explanation: Operatoren
Für Listen gibt es ebenfalls einen + und einen * Operator: <br>
Beim + Operator wird eine Liste erstellt, die erst alle Elemente der ersten Liste und dann alle Elemente der zweiten Liste enthält. <br>
Beim * Operator muss der andere Wert neben der Liste ein integer sein. Es wird eine neue Liste erstellt, welche die Elemente der Liste entsprechend häufig wiederholt.
End of explanation
"""


liste = [0, True, 4.2, "foo"]
index = 0
length = len(liste)
while index < length:
    element = liste[index]
    print(index, element)
    index = index + 1 

"""
Explanation: Zugriff auf Elemente
Auf die Elemente einer Liste wird über deren Index zugegriffen. Dieser ist ein integer startet beim ersten Element mit 0.
End of explanation
"""


liste = [0, True, 4.2, "foo"]
index = -1
length = -1 * len(liste)
while index >= length:
    element = liste[index]
    print(index, element)
    index = index - 1 

"""
Explanation: Es ist allerdings auch möglich mit negativen Indices zu arbeiten. Diese starten beim letzten Element der Liste mit dem Index -1. Dadurch wird es möglich auf die letzten Elemente der Liste zuzugreifen ohne die len() Funktion benutzen zu müssen. <br>
So können wir die Liste von eben auch rückwärts durchlaufen:
End of explanation
"""


liste = [0, True, 4.2, "foo", False, "spam", "egg", 42]
print(liste[1:-1])  # von Index 1 bis Index -1
print(liste[0:6:2])  # von Index 0 bis Index 6 jedes zweite Element
print(liste[-1:0:-1])  # von Index -1 bis Index 0 jedes Element

"""
Explanation: Slicing
Es ist nicht nur möglich auf die gesamte Liste oder auf einzelne Elemente zuzugreifen. Mit slicing können wir auch auf Teile einer Liste zugreifen.
python
liste[start:stop:step]
* start gibt den Index an, an dem unser Teil der Liste startet
* stop gibt den Index an, bis zu dem unser Teil der Liste geht. Das Element mit diesem Index ist jedoch nicht enthalten.
* step gibt die Schrittweite zwischen zwei Indices unserer Teilliste an
End of explanation
"""


liste = [0, True, 4.2, "foo", False, "spam", "egg", 42]
print(liste[2::2])  # von Index 2 bis zum Ende jedes zweite Element
print(liste[:-1:3])  # vom Start bis Index -1 jedes dritte Element
print(liste[::4])  # vom Start bis zum Ende jedes vierte Element
print(liste[1:])  # von Index 1 bis zum Ende

"""
Explanation: start, stop und step können allerdings auch weggelassen werden.
* Wird start weggelassen startet die Teilliste mit dem ersten Element.
* Wird stop weggelassen, endet die Teilliste mit dem letzten Element.
* wird step weggelassen, ist die Schrittweite 1
End of explanation
"""


liste = [0, True, 4.2, "foo", False, "spam", "egg", 42]
print(liste)
liste.append(3.14)
print(liste)

liste = [0, True, 4.2, "foo", False, "spam", "egg", 42]
print(liste)
liste.insert(4, "test")
print(liste)

"""
Explanation: Elemente hinzufügen
Nachdem wir eine Liste erstellt haben, möchten gegebenenfalls auch Elemente hinzufügen. Dazu gibt es mehrere Möglichkeiten. Wir können mit Hilfe der list.append() Methode ein Element an unsere Liste hinten anhängen oder mit der list.insert() Methode ein Element an einem Index in unsere Liste einfügen, wobei die Elemente nach diesem Index nach hinten aufrücken.
End of explanation
"""


liste = [0, True, 4.2, False, "spam", "egg", 42]
print("egg:", "egg" in liste)
print("ham:", "ham" in liste)

"""
Explanation: Elemente finden
Eventuell wollen wir wissen ob ein Element in unserer Liste enthalten ist, und wenn es in unserer Liste enthalten ist, wollen wir eventuell wissen an welcher Stelle oder wie oft.
Zuerst wollen wir lernen, wie wir mit dem Schlüsselwort in feststellen, ob ein Element in unserer Liste enthalten ist:
End of explanation
"""


liste = [0, True, 4.2, False, "spam", "egg", 42]
print("1:", 1 in liste)

"""
Explanation: Aber vorsicht:
End of explanation
"""


liste = [0, True, 4.2, False, "spam", "egg", 42]
print("1:", 1 in liste)
ind = liste.index(1)
print(liste[ind])

"""
Explanation: Scheinbar ist der integer 1 in der Liste enthalten, obwohl keiner der Einträge auf den ersten Blick danach aussieht. Also wird ein Element unserer Liste als 1 interpretiert oder ist == 1. Um rauszufinden an welcher Stelle sich dieses Element befindet können wir die list.index() Methode benutzen. Dabei müssen wir allerdings vorsichtig sein, versuchen wir nämlich den Index eines Elementes zu finden, der nicht in der Liste enthalten ist, erhalten wir einen Fehler.
End of explanation
"""


liste = list("abracadabra")
print(liste)
print("Erstes Auftreten von 'a':", liste.index("a"))
print("Erstes Auftreten von 'b':", liste.index("b"))
print("Erstes Auftreten von 'c':", liste.index("c"))
print("Erstes Auftreten von 'd':", liste.index("d"))

"""
Explanation: Der boolean True auf dem Index 1 wird hier also als 1 erkannt. Dieses Phänomen tritt allerdings nur mit 1 und True und 0 und False auf. Um dieses Problem zu umgehen nutzen wir im folgenden eine Liste mit anderen Elementen.
End of explanation
"""


liste = ['a', 'b', 'r', 'a', 'c', 'a', 'd', 'a', 'b', 'r', 'a']
print("Anzahl von 'a':", liste.count("a"))
print("Anzahl von 'b':", liste.count("b"))
print("Anzahl von 'c':", liste.count("c"))
print("Anzahl von 'd':", liste.count("d"))
print("Anzahl von 'e':", liste.count("e"))

"""
Explanation: Wie wir sehen können zeigt und list.index() lediglich das erste Auftreten eines Elementes an, auch wenn dieses Element mehrfach in der Liste auftaucht.
Um rauszufinden wie häufig ein Element in unserer Liste auftaucht können wir die list.count() Methode benutzen.
End of explanation
"""


liste = ["foo", "test", 23, "spam", "egg", 42]
print(liste)
del liste[0]
print(liste)
# del liste
# print(liste)

"""
Explanation: Dabei sehen wir auch, dass uns die list.count() Methode keinen Fehler gibt, wenn ein Element (im obigen Fall 'e') nicht in der Liste enthalten ist, sondern 0 zurück gibt.
Elemente entfernen
Wenn wir Elemente entfernen wollen, haben wir auch wieder mehrere Möglichkeiten, die sich im Wesentlichen in unserer Herangehungsweise entscheiden. Kennen wir den Index des Elementes, welches wir aus der Liste entfernen möchten, können wir das Schlüsselwort del oder die list.pop() Methode verwenden, kennen wir jedoch das Element, das wir entfernen möchten, benutzen wir die list.remove() Methode.
End of explanation
"""


liste = ["foo", "test", 23, "spam", "egg", 42]
print(liste)
element = liste.pop()
print(liste)
print("Entfernt:", element)

"""
Explanation: Wie wir sehen, können wir nicht nur einzelne Elemente einer Liste anhand ihres Indexes, sondern auch die gesamte Liste entfernen. Das Schlüsselwort del entfernt die Referenz einer Variable und somit die Variable, weshalb wir auch einen NameError erhalten, wenn wir versuchen die Variable zu benutzen, nachdem wir sie gelöscht haben.
End of explanation
"""


liste = ["foo", "test", 23, "spam", "egg", 42]
print(liste)
element = liste.pop(3)
print(liste)
print("Entfernt:", element)

"""
Explanation: Oben haben wir statt des del Schlüsselwortes die list.pop() Methode benutzt. Das hat den Vorteil, dass uns die list.pop() Methode das Element, welches wir aus der Liste entfernt haben, zurück gibt. Wenn wir der list.pop() keinen Index mitgeben, entfernt sie standardmäßig das letzte Element. Wenn wir der list.pop() Methode einen Index geben, entfernt sie das Element an diesem Index aus der Liste.
End of explanation
"""


liste = list("abracadabra")
print(liste)
liste.remove("a")
print(liste)
liste.remove("a")
print(liste)

"""
Explanation: Nun wollen wir ein Element, dessen Wert wir kennen aus der Liste entfernen. Dazu benutzen wir die list.remove() Methode. Diese entfernt das erste Auftreten des Wertes, den wir ihr geben, aus der Liste.
End of explanation
"""


inner_list1 = [0, 1, 2]
inner_list2 = [3, 4, 5]
outer_list = [inner_list1, inner_list2]
print(outer_list)
print("Länge outer_list:", len(outer_list))
print("outer_list[0]:", outer_list[0])
print("outer_list[1]:", outer_list[1])

"""
Explanation: Verschachtelung
Da wir in einer Liste Elemente beliebigen Typs speichern können, können wir auch eine Liste als Element einer Liste speichern. Auf die innere Liste können wir dann genauso zugreifen, wie auf jedes andere Element.
End of explanation
"""


inner_list1 = [0, 1, 2]
inner_list2 = [3, 4, 5]
outer_list = [inner_list1, inner_list2]
# die jeweils ersten Elemente der inneren Listen:
print("Erstes Element von:")
print("outer_list[0]:", outer_list[0][0])
print("outer_list[1]:", outer_list[1][0])

"""
Explanation: Die äußere Liste enthält zwei Elemente, die in diesem Fall jeweils Listen sind.
End of explanation
"""


dir(list)

"""
Explanation: Die vergessenen Methoden
Wenn wir uns mit der dir() Funktion die Methoden eines list Objektes ansehen und alle Einträge mit __ am Anfang und Ende des Namens ignorieren, stellen wir fest, dass wir noch einige Methoden nicht behandelt haben.
End of explanation
"""


dir(list)[-11:]

"""
Explanation: Da uns die dir() Funktion eine Liste zurückgibt, können wir uns diese Methoden ausgeben lassen, indem wir die letzten 11 Elemente anzeigen:
End of explanation
"""


liste = list("abracadabra")
liste.clear()
print(liste)

# Alternativ:
liste = list("abracadabra")
liste = list()
print(liste)

"""
Explanation: list.clear()
Die list.clear() Methode können wir benutzen um alle Elemente einer Liste zu entfernen. Der Anwendungsfall ist relativ begrenzt, da wir auch einfach eine leere Liste erstellen können.
End of explanation
"""


liste = list("abracadabra")
liste_copy = liste.copy()
print(liste)
print(liste_copy)

# Alternativ:
liste = list("abracadabra")
liste_copy = liste[:]
print(liste)
print(liste_copy)

"""
Explanation: list.copy()
Die list.copy() Methode kann benutzt werden um eine Kopie der Liste zu erstellen. Auch hier gibt es eine alternative Möglichkeit über Slicing, dasselbe zu erreichen.
End of explanation
"""


liste = list("abracadabra")
liste.extend("simsala")
print(liste)

# Alternativ:
liste = list("abracadabra")
liste = liste + list("simsala")
print(liste)

"""
Explanation: list.extend()
Die list.extend() Methode kann benutzt werden um an die bestehende Liste eine andere Liste anzuhängen.
End of explanation
"""


liste = list("abracadabra")
liste.reverse()
print(liste)

# Alternativ:
liste = list("abracadabra")
liste = liste[::-1]
print(liste)

"""
Explanation: list.reverse()
Mit der list.reverse() können wir eine Liste umdrehen. Wie auch die voherigen Methoden gibt es auch hier Alternativen.
End of explanation
"""


liste = list("abracadabra")
liste.sort()
print(liste)

"""
Explanation: list.sort()
Mit list.sort() lässt sich eine Liste sortieren, solange die Elemente miteinander vergleichbar sind.
End of explanation
"""


t1 = tuple("abracadabra")
t2 = (2,3,23,42)
t3 = 3,2,3
print(t1)
print(t2)
print(t3)

"""
Explanation: tuple
Ein tuple ist grob gesagt eine unveränderliche Liste. Ein Tupel hat eine Länge, Elemente können nicht entfernt oder hinzugefügt werden. Lediglich die Elemente eines Tupels lassen sich ändern, wenn sie veränderlich sind.
End of explanation
"""


Tuple = tuple("abracadabra")
print(Tuple[4])
print(Tuple[0:5])

"""
Explanation: Ein Tuple lässt sich über die tuple() Funktion oder über runde Klammern () definieren. Die runden Klammern können wir allerdings meistens weglassen. Der Zugriff auf die Elemente funktioniert, wie bei Listen sowohl über den Index, als auch über Slicing.
End of explanation
"""


dir(tuple)

Tuple = tuple("abracadabra")
print(Tuple)
print("Länge:", len(Tuple))
print("Anzahl 'a':", Tuple.count("a"))
print("Erstes 'b':", Tuple.index("b"))
print("e?", "e" in Tuple)

"""
Explanation: Wenn wir uns die Methoden von einem Tuple anschauen, stellen wir fest, dass es nur zwei "normale" Methoden gibt, die wir auch schon von der Liste kennen, nämlich tuple.count() und tuple.index(). Desweiteren können wir auf einen Tuple auch die len() Funktion und das Schlüsselwort in anwenden.
End of explanation
"""


a = 5
b = 10
tmp = a
a = b
b = tmp
print("a:", a)
print("b:", b)

"""
Explanation: Wir können Tuple auch benutzen um den Wert zweier Variablen zu tauschen. Bisher würden wir dafür den Wert einer Variable in einer temporären Variable (tmp) speichern und die Werte so tauschen.
End of explanation
"""


a = 5
b = 10
a, b = b, a
# Alternativ: (a, b) = (b, a)
print("a:", a)
print("b:", b)

"""
Explanation: Durch die Verwendung von Tuplen können wir nun auf unsere temporäre Variable verzichten und den Code lesbarer gestalten:
End of explanation
"""


zauberwort = "abracadabra"
print(zauberwort[0])
print(zauberwort[-1])
teilwort = zauberwort[:4]
print(teilwort)

zauberwort = "abracadabra"
zauberwort.count("a")

zauberwort = "abracadabra"
print(zauberwort.index("b"))

zauberwort = "abracadabra"
print("e?", "e" in zauberwort)

"""
Explanation: Strings
Jetzt da wir Listen und Tuple kennengelernt haben lohnt es sich, nochmal Strings anzuschauen, da wir auch hier auch auf einzelne Zeichen mit dem Index zugreifen können. So wie bei Listen und Tupeln, können wir auch bei Strings die str.count() und str.index() Methoden verwenden.
End of explanation
"""


leer1 = dict()
leer2 = {}

"""
Explanation: dict
Ein Dictionary speichert Werte nicht anhand ihres Indexes, sondern anhand eines Schlüssels.
Erstellen eines dict
Ein leeres Dictionary kann auf zwei Arten erstellt werden:
End of explanation
"""


dict1 = {"name": "Max", "nachname": "Mustermann", "alter": 42}
dict2 = dict([("name", "Martha"), ("nachname", "Musterfrau"), ("alter", 23)])
print(dict1)
print(dict2)

"""
Explanation: Möchten wir ein Dictionary mit Einträgen erstellen, können wir dies entweder durch die dict() Funktion erreichen, in der wir Schlüssel und Werte als Liste von Tuplen mit zwei Elementen übergeben, oder indem wir Schlüssel und Werte durch Doppelpunkte : getrennt in geschweiften Klammern {} als Literale definieren.
End of explanation
"""


beispiel_dict = {0:"integer", True:"boolean", (0,0):"tuple", "s":"strings"}
print(beispiel_dict)

"""
Explanation: Als Schlüssel sind Werte aller unveränderlichen Typen erlaubt, in einem Dictionary müssen die Schlüssel auch nicht denselben Typen haben, es ergibt sich meistens aber, dass die Schlüssel denselben Typen haben.
End of explanation
"""


Max = {"name": "Max", "nachname": "Mustermann", "alter": 42}
Martha = {"name": "Martha", "nachname": "Musterfrau", "alter": 23}
print("Alter:")
print("Max:", Max["alter"])
print("Martha:", Martha["alter"])

"""
Explanation: Zugriff
Der Zugriff auf die Elemente erfolgt dann über den entsprechenden Schlüssel:
End of explanation
"""


Max = {"name": "Max", "nachname": "Mustermann", "alter": 42}
Martha = {"name": "Martha", "nachname": "Musterfrau"}
print("Alter:")
print("Max:", Max.get("alter", 0))
print("Martha:", Martha.get("alter", 0))

"""
Explanation: Alternativ kann für den Zugriff die dict.get() Methode benutzt werden. Diese ermöglicht es auch einen Standardwert anzugeben, wenn der Schlüssel in dem Dictionary nicht vorhanden ist.
End of explanation
"""


Max = {"name": "Max", "nachname": "Mustermann", "alter": 42}
Martha = {"name": "Martha", "nachname": "Musterfrau"}
Martha["alter"] = 23
print(Martha)

"""
Explanation: Den Wert zu einem Schlüssel können wir setzen indem wir einem Schlüssel einen Wert zuweisen, existiert dieser Schlüssel bereits, wird sein Wert überschrieben.
End of explanation
"""


Martha = {"name": "Martha", "nachname": "Musterfrau"}
print("name?", "name" in Martha)
print("alter?", "alter" in Martha)

"""
Explanation: Natürlich kann man auch das in Schlüsselwort mit Dictionaries benutzen:
End of explanation
"""


Martha = {"name": "Martha", "nachname": "Musterfrau"}
print("Anzahl Schlüssel:", len(Martha))

"""
Explanation: Die len() Funktion liefert bei einem Dictionary die Anzahl an Schlüsseln wieder:
End of explanation
"""


dictionary = {0:"integer", True:"boolean", (0,0):"tuple", "s":"strings"}
print(dictionary.keys())  # liefert die Schlüssel als Liste
print(dictionary.values())  # liefert die Werte als Liste
print(dictionary.items())  # liefert Schlüssel und Werte als Tuple in einer Liste

"""
Explanation: items(), keys() und values()
Die drei Methoden dict.items(), dict.keys() und dict.values() sind sich relativ ähnlich, weshalb wir sie zusammen betrachten wollen.
End of explanation
"""


dictionary = {0:"integer", True:"boolean", (0,0):"tuple", "s":"strings"}
value = dictionary.pop(0)
print(dictionary)

"""
Explanation: pop()
Auch Dictionaries besitzen eine dict.pop() Methode.
End of explanation
"""


# Iteration über einen string:
zauberwort = "abracadabra"
for zeichen in zauberwort:
    print(zeichen)

# Iteration über eine Liste:
liste = [0, True, "foo", 42]
for element in liste:
    print(element)

# Iteration über einen Tuple:
Tuple = (1,2,3)
for zahl in Tuple:
    print(zahl)

# Iteration über ein Dictionary:
Max = {"name": "Max", "nachname": "Mustermann", "alter": 42}
for attribut in Max:
    print(attribut, ":", Max[attribut])

# Alternativ:
Max = {"name": "Max", "nachname": "Mustermann", "alter": 42}
for attribut, wert in Max.items():
    print(attribut, ":", wert)

"""
Explanation: Wie wir sehen funktioniert die dict.pop() Methode ähnlich, wie bei den Listen. Der Wert mit dem angegebenen Schlüssel wird zurückgegeben und aus dem Dictionary entfernt.
Die for-Schleife
Die for-Schleife kann benutzt werden um über verschiedene Objekte zu iterieren. Dabei ist die Syntax einer for-Schleife die folgende:
python
for variable in objekt:
    Befehle
* objekt ist dabei das Objekt, über das wir iterieren.
* variable enthält jeweils ein Element aus dem Objekt.
Wir kennen bereits Listen, Tuple, Dictionaries und Strings, über jeden dieser Typen können wir iterieren.
End of explanation
"""


# alle Zahlen von 0 bis 10:
r = range(10)
print(list(r))

# jede zweite Zahl von 2 bis 42:
r = range(2, 42, 2)
print(list(r))

"""
Explanation: range()
Die range() Funktion ist in vielerlei Hinsicht praktisch. Sie ermöglicht es uns auf einfache, gut lesbare Art und Weise Zahlenfolgen zu erzeugen. Warum das so praktisch ist werden wir gleich sehen.
python
range(stop)
range(start, stop[, step])
Wir können range() entweder nur mit einem stop Wert aufrufen (dieser muss ein integer sein), oder mit einem start, einem stop und einen optionalen step Wert. Bei beiden Varianten erhalten wir ein range Objekt.
Diese verhalten sich ähnlich wie die Werte beim Slicing. Geben wir keinen Wert für start an startet unser range Objekt bei 0, geben wir keinen Wert für step an, ist die Schrittweite 1.
End of explanation
"""


# von 0 bis -100 jede zweite Zahl:
r = range(0, -100, -2)
print(list(r))

"""
Explanation: Genauso, wie beim Slicing können die Werte auch negativ sein.
End of explanation
"""


r = range(10000000000000000)
print(r)

"""
Explanation: Ein range Objekt kann auch benutzt werden um sehr große Zahlenreihen zu erzeugen, da die Zahlen erst berechnet werden, wenn sie benötigt werden.
End of explanation
"""


for i in range(10):
    print(i)

"""
Explanation: Dadurch, dass wir über ein range Objekt iterieren können, können wir range() gut in einer for-Schleife benutzen.
End of explanation
"""


zauberwort = "abracadabra#test"
neuer_zauber = ""
for zeichen in zauberwort:
    if zeichen == "#":
        break
    elif zeichen == "a":
        continue
    else:
        neuer_zauber += zeichen
print(neuer_zauber)

string = "Das ist ein Teststring."
for zeichen in string:
    if zeichen == "Y":
        break
else:
    print("Kein 'Y' gefunden.")

"""
Explanation: break, continue und else
Die Schlüsselwörter break, continue und else können wir innerhalb einer for-Schleife genauso benutzen, wie in einer while-Schleife.
End of explanation
"""


table = [
    [
        "test",
        "foo",
        "bar"
    ],
    [
        "ham",
        "spam",
        "egg"
    ]
]
for row in table:
    for entry in row:
        print(entry)

"""
Explanation: for-Schleifen können verschachtelt werden, was wir benutzen können um über ein verschachteltes Objekt zu iterieren.
End of explanation
"""


Set = set()
print(Set)

Set = {2,3,5,7}
print(Set)

"""
Explanation: sets
Sets sind Mengen im mathematischen Sinn. Das bedeutet ein Element kann entweder in einer Menge enthalten sein, oder eben nicht.
Erstellen eines set
Ein Set kann entweder über die set() Funktion aus einem anderen Objekt erzeugt werden, oder über geschweifte Klammern {} welche die Literale eines Sets bilden.
End of explanation
"""


some_primes = {2, 3, 5, 7, 11, 13}
print(12 in some_primes)

some_primes = {2, 3, 5, 7, 11, 13}
print(len(some_primes))

"""
Explanation: in und len()
End of explanation
"""


some_primes = {2, 3, 5, 7, 11, 13}
some_primes.add(17)
print(some_primes)

"""
Explanation: Elemente hinzufügen
Nach dem wir eine Menge erstellt haben, können wir mit der set.add() Methode Elemente hinzufügen.
End of explanation
"""


some_primes = {2, 3, 5, 7, 11, 13}
more_primes = [17, 19, 23]
some_primes.update(more_primes)
print(some_primes)

"""
Explanation: Um nicht nur einzelne Elemente, sondern mehrere Elemente an eine Menge anzuhängen, können wir die set.update() Methode benutzen.
End of explanation
"""


Set = {23, "foo", 42, "test", (1,2)}
element = Set.pop()
print(element)
print(Set)

Set = {23, "foo", 42, "test", (1,2)}
Set.remove("foo")
print(Set)

"""
Explanation: Elemente entfernen
Um Elemente zu entfernen gibt es zwei Möglichkeiten. Die set.pop() Methode entfernt ein Element aus der Menge und gibt es dabei zurück. Die set.remove() Methode kann dafür benutzt werden, um Elemente anhand ihres Wertes aus der Menge zu entfernen.
End of explanation
"""


# Schnittmenge zweier Mengen:
set1 = {1, 3, 4, 5, 2}
set2 = {5, 8, 1, 3, 7, 9}
schnittmenge = set1 & set2
print(schnittmenge)

"""
Explanation: Mengenoperationen
Set-Objekte bieten Mengenoperationen an, die auch aus der Mathematik bekannt sind. Diese sind die Schnittmenge, die Vereinigungsmenge und die Differenzmenge.
Die Schnittmenge enthält alle Elemente, die in beiden Mengen enthalten sind.
End of explanation
"""


# Schnittmenge zweier Mengen:
set1 = {1, 3, 4, 5, 2}
set2 = {5, 8, 1, 3, 7, 9}
schnittmenge = set1.intersection(set2)
print(schnittmenge)

"""
Explanation: Alternativ können wir auch die set.intersection() Methode benutzen.
End of explanation
"""


# Vereinigungsmenge zweier Mengen:
set1 = {1, 3, 4, 5, 2}
set2 = {5, 8, 1, 3, 7, 9}
vereinigung = set1 | set2
print(vereinigung)

"""
Explanation: Die Vereinigungsmenge enthält alle Elemente, die in einer der Mengen enthalten sind.
End of explanation
"""


# Vereinigungsmenge zweier Mengen:
set1 = {1, 3, 4, 5, 2}
set2 = {5, 8, 1, 3, 7, 9}
vereinigung = set1.union(set2)
print(vereinigung)

"""
Explanation: Alternativ können wir auch die set.union() Methode benutzen.
End of explanation
"""


s1 = {1, 3, 4, 5, 2}
s2 = {4, 5}
differenz = s1 - s2
print(differenz)

"""
Explanation: Die Differenzmenge einer Menge S1 mit einer Menge S2 enthält alle Elemente, die in der Menge S1 aber nicht in der Menge S2 sind.
End of explanation
"""


s1 = {1, 3, 4, 5, 2}
s2 = {4, 5}
differenz = s1.difference(s2)
print(differenz)

"""
Explanation: Alternativ können wir auch die set.difference() Methode benutzen.
End of explanation
"""


s1 = {1, 3, 4, 5, 2}
s2 = {4, 5, 6}
sym_differenz = s1 ^ s2
print(sym_differenz)

"""
Explanation: Es gibt auch noch die symmetrische Differenzmenge zweier Mengen. Diese enthält alle Elemente, die in einer der beiden Mengen, aber nicht in beiden Mengen enthalten sind.
End of explanation
"""


s1 = {1, 3, 4, 5, 2}
s2 = {4, 5, 6}
sym_differenz = s1.symmetric_difference(s2)
print(sym_differenz)

"""
Explanation: Alternativ können wir für die symmetrische Differenz auch die set.symmetric_difference() Methode benutzen.
End of explanation
"""


s1 = {1, 3, 4, 5, 2}
s2 = {4, 5, 6}
s3 = {7, 8, 2}
print(s1.isdisjoint(s2))
print(s2.isdisjoint(s3))

"""
Explanation: Mit der set.isdisjoint() Methode können wir testen ob zwei Mengen disjunkt sind. Zwei Mengen sind disjunkt, wenn ihre Schnittmenge leer ist.
End of explanation
"""


s1 = {1, 2, 3, 4, 5}
s2 = {1, 2}
print("Ist s2 eine Teilmenge von s1:")
print(s2.issubset(s1))
print("Ist s1 eine Obermenge von s2:")
print(s1.issuperset(s2))

"""
Explanation: Mit der set.issubset() und der set.issuperset() Methode können wir feststellen, ob eine Menge eine Teilmenge, beziehungsweise eine Obermenge einer anderen Menge ist. Eine Menge s1 ist Teilmenge einer Menge s2, wenn alle Elemente von s1 in der Menge s2 enthalten sind. s2 ist dann die Obermenge von s1.
End of explanation
"""


s1 = {1, 2, 3, 4}
s2 = {1, 2}
print(s2 < s1)
print(s1 > s2)

"""
Explanation: Statt der set.issubset() Methode können wir auch den &lt;= Operator benutzen, statt der set.issuperset() Methode können wir auch den &gt;= Operator benutzen.
Den &gt; Operator können wir benutzen um zu ermitteln, ob eine Menge s1 eine echte Obermenge von  einer Menge s2 ist. Dies ist der Fall, wenn s1 eine Obermenge von s2 und nicht die gleiche Menge ist.
Den &lt; Operator können wir benutzen um zu ermitteln, ob eine Menge s1 eine echte Teilmenge einer Menge s2 ist. Dies ist der Fall, wenn s1 eine Teilmenge von s2 ist und nicht die gleiche Menge ist.
End of explanation
"""


