From 813182e6aa7d4a591910960736e0102b0a70f4be Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 8 Dec 2023 04:17:34 +0100 Subject: [PATCH] added tut 08 --- Tutorium/tut08/README.md | 589 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 589 insertions(+) diff --git a/Tutorium/tut08/README.md b/Tutorium/tut08/README.md index e69de29..2980799 100644 --- a/Tutorium/tut08/README.md +++ b/Tutorium/tut08/README.md @@ -0,0 +1,589 @@ +--- +marp: true +paginate: true +class: invert +# theme: uncover +footer: Tutorium 08 - 08.12.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-08 +header: +--- + + +# Tutorium 08 + +Exercise 07 - Korrektur +Objekt-orientierte Programmierung +Exercise 08 - Hilfe und Fragen + +--- + +# Exercise 07 - Musterlösung + +--- + +# Aufgabe 7.1 a) + +```python +def ask(s: str) -> Optional[bool]: + match input(f"{s}? [Yes / No]: "): + case "Yes" | "yes": + return True + case "No" | "no": + return False + case _: + return None +``` + +- Default-Case `case _:` oder exhaustive pattern matching +- Keinen unnötigen Code-Duplikat mit `|` + +--- + +# Aufgabe 7.1 b) + +```python +class Operator(Enum): + ADD = auto() + MUL = auto() + + +def eval[T: (int, str)](t: tuple[Operator, T, T]) -> Optional[T]: + match t: + case (Operator.ADD, x, y): + return x + y + case (Operator.MUL, int(i), int(j)): + return i * j + case _: + return None +``` +- Default-Case `case _:` oder exhaustive pattern matching +- Generic `T` mit der Einschränkung `T: (int, str)` (nicht `int | str`!) + +--- + +# Aufgabe 7.1 c) + +```python +@dataclass +class Cons[T]: + head: T + tail: Optional["Cons[T]"] = None + + +type LList[T] = Optional[Cons[T]] +``` +Hierbei gab es einige Verwirrung, was wahrscheinlich unter anderem an der Benennung lag + +--- + +# Aufgabe 7.1 c) + +```python +@dataclass +class Node[T]: + value: T + next: Optional['Node[T]'] = None + + +type LinkedList[T] = Optional[Node[T]] +``` + +Wobei jede `Node` auch einen Wert vom Typ `T` hat und weiß was die nächste `Node` in der Liste ist. Also ist `Node` quasi ein Eintrag in der Liste, der sich merkt was als nächstes kommt und einem Wert zugeordnet wird. + +--- + +# Aufgabe 7.1 c) + +```python +def tail[T](xs: LList[T]) -> LList[T]: + match xs: + case None: + return None + case Cons(_, tail): + return tail + +def len(xs: LList[Any]) -> int: + match xs: + case None: + return 0 + case Cons(_, tail): + return 1 + len(tail) + +``` + + +--- + +das geht aber auch schöner: + +```python +def next[T](xs: LinkedList[T]) -> LinkedList[T]: + return xs.next if xs else xs + +def len(xs: LinkedList[Any]) -> int: + return 1 + len(xs.next) if xs else 0 +``` + +--- + +# Aufgabe 7.2 - BTree Definition + +```python +@dataclass +class Node[T]: + mark: T + left: Optional["Node[T]"] = None + right: Optional["Node[T]"] = None + + +type BTree[T] = Optional[Node[T]] +``` +- Binärbaum +- generisch und rekursiv definiert wie unsere `LList[T]` + +--- + +# Aufgabe 7.2 a) + +```python +def contains[T](tree: BTree[T], val: T) -> bool: + return tree and (tree.m == val or contains(tree.left, val) or contains(tree.right, val)) +``` +- wir testen ob der aktuelle `tree` eben `None` ist +- dann testen wir ob der aktuelle Wert unserem gesuchten entspricht +- Wenn das nicht der Fall ist laufen wir den Baum rekursiv ab +- Wichtig ist dass man Links **und** Rechts abläuft, und nicht einen Zweig vergisst + +--- + +# Aufgabe 7.2 b) + +```python +def leaves[T](tree: BTree[T]) -> list[T]: + match tree: + case None: + return [] + case Node(mark, None, None): + return [mark] + case Node(_, left, right): + return leaves(left) + leaves(right) +``` +- exhaustive matchen oder default-case! + +--- + +# Aufgabe 7.2 - AST Definition + +```python +type AST = BTree[int | str] +``` +- Schlechtes Design, weil `str` kann alles sein, wir gehen nur davon aus dass es `"+"` oder `"*"` ist. +- Man hätte auch einfach den `Enum` aus Aufgabe 1 verwenden können... + +```python +class Op(Enum): + ADD = "+" + MUL = "*" + +type AST = BTree[int | Op] +``` +--- + +# Aufgabe 7.2 c) + +```python +def evaluate(tree: AST) -> Optional[int]: + match tree: + case Node(int(i), None, None): + return i + case Node("*" | "+", left, right): + left = evaluate(left) + right = evaluate(right) + if left is None or right is None: + return None + if tree.mark == "+": + return left + right + else: + return left * right + case _: + return None +``` + +--- + +```python +def evaluate(tree: AST) -> Optional[int]: + match tree: + case Node(int(i), None, None): + return i + case Node(Op.MUL | Op.ADD, left, right) if left and right: + left = evaluate(left) + right = evaluate(right) + if tree.mark == Op.ADD: + return left + right + else: + return left * right + case _: + return None +``` + +--- + +# Aufgabe 7.2 d) + +```python +def infix_str(tree: AST) -> str: + match tree: + case Node(int(i), _, _): + return str(i) + case Node(str(s), left, right): + return f"({infix_str(left)} {s} {infix_str(right)})" + case _: + return "" +``` + +- Das wichtigste war die Reihenfolge der Rekursiven aufrufe. +- Hier also `infix_str(left) + s + infix_str(right)` + +--- + +```python +def prefix_str(tree: AST) -> str: + match tree: + case Node(int(i), _, _): + return str(i) + case Node(str(s), left, right): + return f"({s} {prefix_str(left)} {prefix_str(right)})" + case _: + return "" + +def postfix_str(tree: AST) -> str: + match tree: + case Node(int(i), _, _): + return str(i) + case Node(str(s), left, right): + return f"({postfix_str(left)} {postfix_str(right)} {s})" + case _: + return "" +``` + +--- + +# Fragen? + +--- + +# Probleme bei Exercise 07 + +- Generics `T` wurden unnötig verwendet + - Ihr müsst keine Generics verwenden wenn nicht nötig + - Sowas wie `T: int` ist unnötig, schreibt einfach direkt `int` +- Rekursion als Konzept - dabei hilft nur Üben + - begegnet einem aber auch unglaublich selten in der echten Welt + - wenn es Verständnis-Fragen gibt einfach melden +- `@dataclass` mit `Enum` verwendet (dabei geht alles kaputt) + +--- + +# Objekt orientiertes Programmieren - OOP + +--- + +# Definitionen in der OOP - Klassen + +- Eine Klasse ist wie ein Bauplan +- Jede Klasse definiert die Eigenschaften und das Verhalten +- Verhalten sind Methoden also `def` +- Eigenschaften sind Attribute, `int`, `float`, `str`, `list`, ... +- Die Eigenschaften definieren den Zustand +- Eigenschaften können sich ändern + +--- + +# Beispiel - Cat + +```python +from dataclasses import dataclass + +@dataclass +class Cat: + age: int + weight: float + name: str + + def meow(self): + print("Meow") +``` + +--- + +# Beispiel - Dog + +```python +from dataclasses import dataclass + +@dataclass +class Dog: + age: int + weight: float + name: str + + def woof(self): + print("Woof") +``` +--- + +# Objekte erzeugen + +```python +dog = Dog(age=3, weight=50.0, name="dog") +cat = Cat(age=7, weight=4.5, name="cat") +dog.woof() # Woof +cat.meow() # Meow +``` + +--- + +# Objekte erzeugen + +```python +dog = Dog(3, 50.0, "dog") +cat = Cat(7, 4.5, "cat") +dog.woof() # Woof +cat.meow() # Meow +``` +# Zustand ändern +```python +cat.age = 8 +print(cat.age) # 8 +``` + +--- + +# Vererbung + +- Was haben `Cat` und `Dog` gemeinsam? + - `age` + - `weight` + - `name` + +=> Lösung ist eine Oberklasse `Animal` + +--- + +# Vererbung - Animal + +```python +@dataclass +class Animal: + age: int + weight: int + name: str +``` + +```python +@dataclass +class Cat(Animal): + pass + +@dataclass +class Dog(Animal): + pass +``` + +--- + +# Bisher in der Vorlesung + +```python +def make_noise(animal: Animal): + match animal: + case Dog(): + print("Woof") + case Cat(): + print("Meow") + case _: + pass +``` + +--- + +# Bisher in der Vorlesung + +```python +def make_noise(animal: Animal): + match animal: + case Dog(): + print("Woof") + case Cat(): + print("Meow") + case _: + pass +``` + +das ist kein gutes Design, warum? + +--- + +was ist wenn wir jetzt eine neue Klasse `Mouse` erstellen wollen. + +```python +@dataclass +class Mouse(Animal): + pass +``` + +--- + +jetzt müssen wir `Mouse` zu `make_noise` hinzufügen + +```python +def make_noise(animal: Animal): + match animal: + case Dog(): + print("Woof") + case Cat(): + print("Meow") + case Mouse(): + print("Peep") + case _: + pass +``` + +das kann ziemlich nervig werden. Vor allem wenn wir größere Projekte haben + +--- + +# Polymorphism + +> Die Polymorphie der objektorientierten Programmierung ist eine Eigenschaft, die immer im Zusammenhang mit Vererbung und Schnittstellen (Interfaces) auftritt. Eine Methode ist polymorph, wenn sie in verschiedenen Klassen die gleiche Signatur hat, jedoch erneut implementiert ist. + +--- + +```python +@dataclass +class Animal: + age: int + weight: float + name: str + + def make_noise(self) -> None: + pass + +@dataclass +class Cat(Animal): + + def make_noise(self) -> None: + print("Meow") + +@dataclass +class Dog(Animal): + + def make_noise(self) -> None: + print("Woof") +``` + +--- + +# Geht das schöner in Python 3.12? + +Ja, mit `override` sagt sogar pylance bescheid wenn wir eine polymorphe Methode falsch überschrieben haben! +```python +form dataclasses import dataclass +from typing import override + +@dataclass +class Cat(Animal): + + @override + def make_noise(self) -> None: + print("Meow") +``` + +--- + +# Unsere neue polymorphe Methode benutzen + +```python +@dataclass +class Zoo: + animals: list[Animal] = [] + + +zoo = Zoo(animals=[ + Cat(age=6, weight=4.5, name="Milow"), + Cat(age=7, weight=5.0, name="Blacky"), + Dog(age=12, weight=40.3, name="Bernd") + ]) + +for animal in zoo.animals: + animal.make_noise() # Meow + # Meow + # Woof +``` + +--- + +# Oberklasse Animal + +- Was passiert wenn man `Animal` euzeugt? + - Wir haben ein Objekt was eigentlich nicht erzeugt werden sollte + - Es ist eine Art Schablone für alle Klassen von Typ `Animal` + +Können wir die Instanziierung verhindern? Ja! + +--- + +# Abstrakte Klassen + +- abstrakte Klassen sind kein konkreter Bauplan für ein Objekt +- stattdessen eine Schablone für weiter Baupläne +- `Animal` ist so einen Schablone für alle Tiere + +--- + +# Abstrakte Klassen - in anderen Programmiersprachen +Beispiel Java mit `abstract` +```java +abstract class Animal { + private int age; + private float weight; + private String name; + + abstract void make_noise(); +} + +class Cat extends Animal { + @override + void make_noise() { + System.out.println("Meow") + } +} +``` +--- + +# Abstrakte Klassen - Python + +- `ABC` aus dem Modul `abc`. +- Ja sie mussten `abstract` wirklich abkürzen mit `ABC` (Abstract Base Class) +- für abstrakte Methoden gibt es die Annotation `@abstractmethod` im selben Modul + +```python +from abc import ABC, abstractmethod + +class Animal(ABC): + age: int + weight: float + name: str + + @abstractmethod + def make_noise(): + pass +``` + +--- + +# Fragen? + +--- + +# Exercise 08