Files
eidp-2023/Tutorium/tut06
Nils Pukropp 1ac3e3d4fe tutorium 06
2023-11-24 07:06:05 +01:00
..
2023-11-24 07:06:05 +01:00
2023-11-24 07:06:05 +01:00

Tutorium 06 - 24.11.2023

Vorab Informationen

  • Kollektiver Discord 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
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:

def some_func[T](some_list: list[T]) -> T:
    # ...

kleines Beispiel von "Bounds" aus Rust:

fn some_func<T: Add>(some_list: Vec<T>) -> T {
    // ...
}

oder noch schöner

fn some_func<T>(some_list: Vec<T>) -> T 
where T: Add<Output = T> + Default,
{
    // ...
}
@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()
type Optional[T] = T | None

Python ist nicht statisch typisiert (statically-typed)! Bedeutet trotz annotation könnt ihr machen was ihr wollt:

def add(x: int, y: int) -> int:
    return x + y

add(2, 2.0) # 4.0

genauso ist es bei Generics:

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
@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

      @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

      type Point[T] = Point1D[T] | Point2D[T] | Point3D[T]
      
    • Nun können wir den Type Alias mit Pattern-Matching auf den eigentlichen Datentypen reduzieren

      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
      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!
      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

    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!

    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

    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

@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