# %% [markdown]
# # Erweiterte Datentypen: Container

# %% [markdown]
# ## Listen und Tupel

# %%
# Listenliterale

values = [5, 10, 20, 30, 50]
print(values)
fractions = [0.5, 0.6, 0.8, 1.0, 1.2]
print(fractions)
names = ["Joe", "Alice", "Bob", "Charly"]
print(names)
heap = [1, "Joe", 1.4, "Alice", 100]
print(heap)
single = ["one"]
leer = []
auch_leer = list()
print(single, leer, auch_leer)

# %%
# Tuplelliterale
values = (5, 10, 20, 30, 50)
print(values)
fractions = (0.5, 0.6, 0.8, 1.0, 1.2)
print(fractions)
names = ("Joe", "Alice", "Bob", "Charly")
print(names)
heap = (1, "Joe", 1.4, "Alice", 100)
print(heap)
t_single = ("one",)
t_auch_single = "one",
t_leer = ()
t_auch_leer = tuple()
print(t_single, t_auch_single, t_leer, t_auch_leer)


# %%
# Umwandlung

l = [1, 2, 3, 4, 5]
t = tuple(l)
print(l, t)
l = [10, 20, 30, 40, 50]
print(l, t)
l2 = list(t)
print(l2)

# %%
s = "hallo"
print(list(s))
print(tuple(s))

# %% [markdown]
# ### Indizierung

# %%
seasons = ["Frühling", "Sommer", "Herbst", "Winter"]
print(seasons[0])   # Erstes Element hat Index 0!
print(seasons[2])
print(seasons[-1])  # Negative Indices zählen vom letzten Element rückwärts
print(seasons[-3])

# %%
# Ungültige Indizes führen zu einem Fehler (IndexError Exception)
print(seasons[4])  # 4 Elemente => gültige Indizes 0..3

# %%
# Ebenso
print(seasons[-5])

# %%
# Zuweisung an einzelne Listenelemente

l = [1, 2, 3, 4, 5]
l[1] = -1
print(l)
l[3] = "vier"
print(l)
l[-1] = 5.0
print(l)

# %%
# Indizierung funktioniert auch mit Tupeln mit der selben Syntax
t = (1, 2, 3, 4, 5)
print(t[0], t[-2])

# %%
# Aber Tuple sind nicht-veränderbar (immutable)

t[2] = "drei"

# %% [markdown]
# ### Slicing

# %%
l = list(range(10))  # l = [0, 1, ..., 9]
print(l[2:4])  # Start ist inklusiv, Ende nicht!

# %%
# Auch wenn kein oder nur ein Element "übrig bleibt", ist das Ergebnis eine Liste

print(l[2:3])
print(l[9:-1])

# %%
# Start und End können leer, also "offen", sein

print(l[5:])
print(l[:3])  # auch hier ist der Ende-Index nicht-inklusiv!

# %%
# Sind Start und Ende leer, ist das Ergebnis eine Kopie der Liste / des Tuples

l = [1, 2, 3]
l2 = l[:]
l[0] = 999
print(l)
print(l2)

# %%
# Zuweisung an Listenslices ist möglich!

seasons = ["Winter", "Frühling", "Sommer", "Herbst"]
seasons[1:1] = ["Karneval"]
print(seasons)

# %%
# Listen oder Tuples können mit + zu einer/m neuen Liste / Tupel zusamengefügt werden 

items = [1, 2, 3]
more_items = items + ["one", "two", "three"]
print(more_items)
items[1:2] = []
print(items)
print(more_items)
more_items += [4, 6, 6]
print(more_items)


# %%
t = (1, 2, 3)
t2 = t + (4, 5, 6)
print(t)
print(t2)

# %%
# Achtung: 

t = (1, 2, 3)
t2 = t
print(t)
print(t2)
print(t is t2)
t += (1, 2, 3)   # Tuples sind immutable, t ist eine neue Kopie!
print(t)
print(t2)
print(t is t2)


# %% [markdown]
# ## Dictionaries

# %%
# Auch Hashtables, Lookup Tables usw. genannt
# Tabellen mit Zurdnung von Schlüsseln zu werten (Key-value pairs)

d = {}
# oder:
d = dict()
print(d)

# %%
scores = {
    "blue team": 40,
    "green team": 45,
    "red team": 30,
    "black team": 60
}
print(scores)

# %%
# Look up

print("The red team scored:", scores["red team"])

# %%
# Neue Key/Value Paare hinzufügen (Dictionaries sind veränderbar!)

scores["white team"] = 45
scores["rainbow team"] = 50
print(scores)

# %%
# Umwandlung

print(tuple(scores))  # Tupel der Keys

print(list(scores))  # Liste der Keys

print(tuple(scores.keys()))

# %%
# Iteration

for key in scores:
    print("Das", key, "hat", scores[key], "Punkte.")

# %%
# Iteration über Key/Value Paare

print(scores.items())

for key, value in scores.items():
    print("Das", key, "hat", value, "Punkte.")

# %%
# Keys müsen keine Strings, aber immutable sein
# => Integers, Floats, Tuples sind ok

numbers = {1: "one", 2: "two", 3: "three"}
print(numbers[2])  # Achtung: nicht mit Listenindex verwechseln!

pixels = {
    (0, 5): 'r',
    (2, 7): 'b', 
    (3, 10): 'r',
    (5, 2): 'g',
    (5, 5): 'b', 
}

for row in range(6):
    for col in range(10):
        if (row, col) in pixels:
            print(pixels[(row, col)], end="")
        else:
            print(".", end="")
    print()


# %% [markdown]
# ## Sets (Mengen)
# 
# Mengen sind wie unsortierte Tupel, in denen jedes Element nur einmal vorkommen kann.

# %%
s = set([])
print(s)   # leeres Set wird repräsentiert mit 'set()', nicht '{}' (leeres Dictionary!)

s2 = set([2,3,5])
s2.add(4)
s2.add(5)  # Schon im Set enthalten
print(s2)

for i in s2:
    print(i)

print()

s2.add(1)
s2.remove(4)

for i in s2:
    print(i)   # Reihenfolge nach Änderung nicht garantiert gleich!

# %%
# Kombination

s1 = set([1, 2, 3, 5])
s2 = set([4, 5, 6, 7])

# Union (alle Elemente aus beiden Sets)
print(s1 | s2)

# Intersection (alle Elemente, die sowohl in s1 als auch s2 sind)
print(s1 & s2)

# Difference (alle Elemente in s1, die nicht auch in s2 sin)
print(s1 - s2)

# %% [markdown]
# Siehe: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset


