diff --git a/Tutorium/tut06/README.md b/Tutorium/tut06/README.md index e69de29..17b34b4 100644 --- a/Tutorium/tut06/README.md +++ b/Tutorium/tut06/README.md @@ -0,0 +1,282 @@ +# Tutorium 06 - 24.11.2023 + +## Vorab Informationen + +- Kollektiver [Discord](https://s.narl.io/s/discord-invite) mit Tutorium 05 (Daniel Mironow) + - Dani's-Tutorium: Mi 16:00 - 18:00, Geb. 106, SR 00 007 + - Im Discord könnt ihr euch direkt mit uns Tutoren austauschen oder untereinander +- Invite: https://s.narl.io/s/discord-invite +- Es gibt wieder einen +
+ QR-Code: + +
+ +## Korrektur Blatt 05 + +- am Samstag, ich hab mich etwas vertan bei der Korrektur +- Punkteverteilung und häufige Fehler werden hier hinzugefügt + +### Häufige Fehler + +- Type annotation +- `@dataclass` nicht benutzt +- mutieren von erstellten Objekt + +## Vorrechnen + +1. Python-Game + 1. `Vec2`: + 2. `add_vecs`: + 3. `Item`: + 4. `Snake`: + 5. `Game`: + 6. `turn_direction`: + 7. `grow_positions`: + 8. `collision`: + 9. `generate_item`: + 10. `pick_item`: + +## Recap - Was ist neu? + +### Union-Type und Type Definitionen + +- neues `type` Keyword +- mit `|` lassen sich Union-Types definieren + +```py +type Number = int | float | complex +``` + +--- + +### Generics (Typvariabeln) + +Manchmal weiß man nicht welcher Typ genau gemeint ist, möchte aber trotzdem "sicherstellen" dass es sich nicht um zwei unterschiedliche handelt: + +```py +def some_func[T](some_list: list[T]) -> T: + # ... +``` +kleines Beispiel von "Bounds" aus Rust: + +```rust +fn some_func(some_list: Vec) -> T { + // ... +} + +``` + +oder noch schöner + +```rust +fn some_func(some_list: Vec) -> T +where T: Add + Default, +{ + // ... +} +``` + +```py +@dataclass +class Stack[T]: + internal_list: list[T] + + def push(self, item: T) -> None: + self.internal_list.append(item) + + def pop(self) -> T | None: + if len(self.internal_list) == 0: + return None + return self.internal_list.pop() +``` + +```py +type Optional[T] = T | None +``` +Python ist nicht statisch typisiert (statically-typed)! Bedeutet trotz annotation könnt ihr machen was ihr wollt: + +```py +def add(x: int, y: int) -> int: + return x + y + +add(2, 2.0) # 4.0 +``` + +genauso ist es bei Generics: + +```py +from mylist import MyList + +lst: MyList[int] = MyList() +lst.push_back(0) +lst.push_back(1) +print(lst) # [0, 1] +lst.push_back("haha not a number") +print(lst) # [0, 1, haha not a number] +``` + +--- + +### self + +- mit `self` ruft man das Objekt wessen Verhalten man modelliert + - damit kann das Objekt verändert (mutiert) werden + - einfache Datenklassen bekommen Objekte die ein Verhalten modellieren +- jede Methode eines Objekt bekommt `self` als ersten Parameter und gibt vor wie sich ein Objekt verhält + +```python +@dataclass +class MyNumber[T](): + number: T + + def add(self, value: T) -> T: + self.number += value + return self.number + +num = MyNumber(3) +print(num.add(5)) # 8 +print(num.number) # 8 +``` + +--- + +### Pattern-Matching + +Mit dem `match` Keyword lassen sich verschiedene Bedingungen *matchen* + +- Zunächst Types: + - Wir erstellen die Datenklassen + + ```python + @dataclass + class Point1D[T]: + x: T + + + @dataclass + class Point2D[T]: + x: T + y: T + + + @dataclass + class Point3D[T]: + x: T + y: T + z: T + ``` + - Wir erstellen einen Typ Alias `Point` + ```py + type Point[T] = Point1D[T] | Point2D[T] | Point3D[T] + ``` + - Nun können wir den Type Alias mit Pattern-Matching auf den eigentlichen Datentypen reduzieren + ```py + def print_point[T](pt: Point[T]) -> None: + match pt: + case Point1D(x): + print(f"Point1D: ({x})") + case Point2D(x, y): + print(f"Point2D: ({x}, {y})") + case Point3D(x, y, z): + print(f"Point3D: ({x}, {y}, {z})") + case _: + print("Not a point!") + ``` +- Aber auch Bedingungen wie Werte + - Nun erweitern wir unsere `print_point` um Nullpunkte auszugeben + ```py + match pt: + case Point1D(0) | Point2D(0, 0) | Point3D(0, 0, 0): + print("Nullpunkt!") + case Point1D(x): + print(f"Point1D: ({x})") + case Point2D(x, y): + print(f"Point2D: ({x}, {y})") + case Point3D(x, y, z): + print(f"Point3D: ({x}, {y}, {z})") + case _: + print("Not a point!") + ``` + - Achtung: Reihenfolge der Cases ist wichtig! + ```py + match pt: + case Point1D(x): + print(f"Point1D: ({x})") + case Point2D(x, y): + print(f"Point2D: ({x}, {y})") + case Point3D(x, y, z): + print(f"Point3D: ({x}, {y}, {z})") + case Point1D(0) | Point2D(0, 0) | Point3D(0, 0, 0): + print("Nullpunkt!") + case _: + print("Not a point!") + ``` +- Guards + + ```py + match pt: + case Point1D(x) if x == 0: + print("1-D Nullpunkt!") + case Point1D(x): + print(f"Point1D: ({x})") + case Point2D(x, y): + print(f"Point2D: ({x}, {y})") + case Point3D(x, y, z): + print(f"Point3D: ({x}, {y}, {z})") + case _: + print("Not a point!") + ``` +- Noch mehr Types *matchen*! + ```py + match pt: + case Point1D(int): + print(f"Ganzzahliger Punkt! {pt.x}") + case Point1D(float): + print(f"Gleitkomma Punkt! {pt.x}") + # ... + ``` +- Und es wird immer seltsamer + + ```py + match some_list: + case ["🤡", *other]: + print(f"your list starts with 🤡 and the rest is {other}") + ``` +## Blatt 06 + +- Fragen? + +## Aufgabe: eigene Liste implementieren + +Implementiere eine generische Liste mit `append`, `get` und `remove`, ohne buildin Listen zu verwenden! + +### Konzept einer (einfach verketteten) Liste + +- Es gibt einen Listeneintrag `Element`, der den eigentlichen Wert des Eintrags `value` beinhaltet und einen Verweis auf das nächste Element `next` in der Liste +- Um dann einen Eintrag `x` zu finden muss man nur `x`-mal die Liste ablaufen und den Wert auslesen +- Wenn man ein Element hinzufügen will muss man lediglich ans Ende der Liste laufen und ein neuen Eintrag erstellen +- Wenn man ein Element entfernen will muss man lediglich das nächste Element vom vorherigen auf das nächste Element vom zu entfernenden setzen + +### Hilfestellung + +```py +@dataclass +class Element[T]: + value: T + next: 'Element[T] | None' + +class MyList[T]: + head: Element[T] | None + length: int + + def append(self, value: T) -> None: + pass + + def get(self, index: int) -> T | None: + pass + + def remove(self, index: int) -> T | None: + pass +``` + diff --git a/Tutorium/tut06/src/mylist.py b/Tutorium/tut06/src/mylist.py new file mode 100644 index 0000000..f68ddd6 --- /dev/null +++ b/Tutorium/tut06/src/mylist.py @@ -0,0 +1,131 @@ +from dataclasses import dataclass + + +@dataclass +class Element[T]: + value: T + next: 'Element[T] | None' + + +@dataclass +class MyList[T]: + head: Element[T] | None = None + last: Element[T] | None = None + length: int = 0 + + def push_back(self, value: T): + if not self.head: + self.head = Element(value, None) + self.last = self.head + self.length += 1 + return + + self.last.next = Element(value, None) + self.last = self.last.next + self.length += 1 + + def remove(self, index: int) -> T | None: + if self.length <= index: + return + + if index == 0: + self.head = self.head.next + self.last = self.head + self.length -= 1 + + i = index + previous = self.head + current = self.head + while current and i > 0: + previous = current + current = current.next + i -= 1 + + if i != 0 or not current: + return + if not current.next: + self.last = previous + previous.next = current.next + self.length -= 1 + return current.value + + def __str__(self) -> str: + output = "" + current = self.head + while current: + output += str(current.value) + if current.next: + output += ", " + current = current.next + return f"[{output}]" + + def __getitem__(self, index: int) -> T | None: + if self.length <= index: + return + + current = self.head + i = index + + while current and i > 0: + current = current.next + i -= 1 + + if i == 0 and current: + return current.value + + def __setitem__(self, index: int, new_value: T): + if self.length <= index: + return + + current = self.head + i = index + + while current and i > 0: + current = current.next + i -= 1 + + if i == 0 and current: + current.value = new_value + + def __len__(self) -> int: + return self.length + + def __iter__(self) -> 'MyList.Iter[T]': + return MyList.Iter(self.head) + + @dataclass + class Iter[E]: + current: Element[E] + + def __next__(self) -> E: + if not self.current: + raise StopIteration + val = self.current.value + self.current = self.current.next + return val + + +if __name__ == "__main__": + lst: MyList[int] = MyList() + lst.push_back(0) + lst.push_back(3) + lst.push_back(2) + assert lst[0] == 0 and lst[1] == 3 and lst[2] == 2 + print(lst) + lst.remove(1) + assert lst[0] == 0 and lst[1] == 2 + print(lst) + lst[1] = 3 + assert lst[0] == 0 and lst[1] == 3 and len(lst) == 2 + print(lst) + assert lst.remove(1) + assert lst.last.value == 0 + lst.remove(0) + assert len(lst) == 0 + assert not lst.head + assert not lst.last + + for i in range(0, 10): + lst.push_back(i) + for it in lst: + print(it) diff --git a/Tutorium/tut06/src/old/problem_with_none.py b/Tutorium/tut06/src/old/problem_with_none.py new file mode 100644 index 0000000..2282d70 --- /dev/null +++ b/Tutorium/tut06/src/old/problem_with_none.py @@ -0,0 +1,20 @@ +from union_types import FilteredList, is_even + + +class PrintableInt(int): + def print(self): + print(self) + + +if __name__ == "__main__": + lst: FilteredList[PrintableInt] = FilteredList(filter=is_even) + lst.append(PrintableInt(0)) + lst.get(0).print() + match lst.get(1): + case None: + pass + case PrintableInt(n): + n.print() + + lst.append(PrintableInt(2)) + lst.get(1).print() diff --git a/Tutorium/tut06/src/old/union_types.py b/Tutorium/tut06/src/old/union_types.py new file mode 100644 index 0000000..c732146 --- /dev/null +++ b/Tutorium/tut06/src/old/union_types.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from typing import Callable + +type Optional[T] = T | None + + +@dataclass +class FilteredList[E]: + lst: list[E] + filter: Callable[[E], bool] + + def __init__(self, filter=lambda _: True): + self.lst = [] + self.filter = filter + + def append(self, item: E): + if self.filter(item): + self.lst += [item] + + def get(self, index: int) -> Optional[E]: + if index < len(self.lst): + return self.lst[index] + + def __str__(self) -> str: + return str(self.lst) + + +def is_even(n: int) -> bool: + return n % 2 == 0 + + +if __name__ == "__main__": + filter_list = FilteredList(filter=is_even) + filter_list.append(0) + print(filter_list) + filter_list.append(2) + print(filter_list) + filter_list.append(3) + print(filter_list) diff --git a/Tutorium/tut06/src/points.py b/Tutorium/tut06/src/points.py new file mode 100644 index 0000000..ebab5a0 --- /dev/null +++ b/Tutorium/tut06/src/points.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass + + +@dataclass +class Point1D[T]: + x: T + + +@dataclass +class Point2D[T]: + x: T + y: T + + +@dataclass +class Point3D[T]: + x: T + y: T + z: T + + +type Point[T] = Point1D[T] | Point2D[T] | Point3D[T] + + +def print_point[T](pt: Point[T]) -> None: + match pt: + case Point1D(0) | Point2D(0, 0) | Point3D(0, 0, 0): + print("Nullpunkt!") + case Point1D(x): + print(f"Point1D: ({x})") + case Point2D(x, y): + print(f"Point2D: ({x}, {y})") + case Point3D(x, y, z): + print(f"Point3D: ({x}, {y}, {z})") + case _: + print("Not a point!") + + +def match_list(some_list: list[str]) -> None: + match some_list: + case ["🤡", *other]: + print(f"your list starts with 🤡 and the rest is {other}") + case _: + print("your list doesn't start with 🤡") + + +if __name__ == "__main__": + print_point(Point1D(1)) # (1) + print_point(Point2D(1, 2)) # (1, 2) + print_point(Point3D(1, 2, 3)) # (1, 2, 3) + print_point(Point3D(0, 0, 0)) # (1, 2, 3) + print_point("not a point") # Not a point! + match_list(["🤡", "ich", "hasse", "python", "manchmal"]) diff --git a/Tutorium/tut06/src/problem_with_annotations.py b/Tutorium/tut06/src/problem_with_annotations.py new file mode 100644 index 0000000..0e3b489 --- /dev/null +++ b/Tutorium/tut06/src/problem_with_annotations.py @@ -0,0 +1,8 @@ +from mylist import MyList + +lst: MyList[int] = MyList() +lst.push_back(0) +lst.push_back(1) +print(lst) # [0, 1] +lst.push_back("haha not a number") +print(lst) # [0, 1, haha not a number] diff --git a/src/img/discord-invite.png b/src/img/discord-invite.png new file mode 100644 index 0000000..104652f Binary files /dev/null and b/src/img/discord-invite.png differ