added tut01

This commit is contained in:
Nils Pukropp
2024-10-17 16:09:29 +02:00
parent 5006902cc5
commit c1f4948cc8
80 changed files with 264 additions and 5418 deletions

View File

@ -1,140 +0,0 @@
# Unterschiede zu Vorjahren
## Typalias
Manchmal braucht man eine Kombination aus Datentypen
### Vorher
Erstellen von Typaliasen mit `Union`
```python
from typing import Union
# `RealNumber` ist also eine `int` oder eine `float`
RealNumber = Union[int, float]
# also geht hier
# `add(1, 1) -> 2`
# `add(1.0, 1.0) -> 2.0`
# aber auch
# `add(1, 1.0) -> 2.0`
def add(x: RealNumber, y: RealNumber) -> RealNumber:
return x + y
```
### In Python3.12
Wir haben jetzt das `type`-Keyword für das wir nichts importieren müssen und können einfach Datentypen mit `|` *odern*
```python
# `type` statt `Union`
type NewRealNumber = int | float
# sonst sieht alles gleich aus
def new_add(x: NewRealNumber, y: NewRealNumber) -> NewRealNumber:
return x + y
```
## Generiche Typ-Parameter (Generics, Typvariabeln, ...)
### Vorher
Erstellen von generischen Typvariabeln/Typ-Parametern mit `TypeVar` aus `typing`:
```python
from typing import Generic, TypeVar, Optional
from dataclasses import dataclass, field
# T ist unser Platzhalten für einen beliebigen Datentyp
T = TypeVar('T')
# jetzt können wir `T` einfach als Platzhalter für einen beliebigen Datentyp verwenden
# praktisch weil wir so wissen dass `x`, `y` und der `return` zumindest den **selben** Datentyp verwenden
# aber welcher genau wissen wir nicht bis jemand die Funktion mit z.b. `int` oder `float`!
# Zum Beispiel bedeutet dass
# `add(1, 2) == 3` => `T == int`
# `add(1.0, 2.0) == 3.0` => `T == float`
# ...
def add(x: T, y: T) -> T:
return x + y
# Ebenso können wir `T` auch für Klassen verwenden
@dataclass
# wir müssen hier festlegen dass T in der Klasse verwendet wird
# indem `MyList` von `Generic[UnsereGenerischeVariabel]` erbt
class MyList(Generic[T]):
def __post_init__(self, ):
# wir haben intern eine `internal_list` die von außen nicht sichtbar ist
self.__internal_list: list[T] = field(default_factory=list)
# hier speichern wir uns die aktuelle Länge unserer Liste
self.__length: int = 0
def append(self, value: T):
"""fügt ein element mit dem Typ `T` hinzu"""
self.__internal_list += [value]
self.__length += 1
def remove(self, value: T):
"""entfert das element `value`, wenn es in unserer Liste ist"""
if value in self.__internal_list:
self.__internal_list.remove(value)
def get(self, i: int) -> Optional[T]:
"""gibt das element an `i` Stelle zurück. Wenn Liste kleiner als `i` ist, dann `None`"""
if i < len(self):
return self[i]
return None
def __len__(self) -> int:
return self.__length
def __getitem__(self, i: int) -> T:
return self.__internal_list[i]
```
### In Python3.12
Deklaration der Typvariabel direkt im Funktionskopf/Klassenkopf mit `[]`.
Sprich wir brauchen `TypeVar` nicht mehr
```python
from typing import Optional
from dataclasses import dataclass, field
# hier wird `V` durch `[V]` deklariert und kann in der Funktion benutzt werden
def new_add[V](x: V, y: V) -> V:
return x + y
# hier wird `T` für die Klasse `NewMyList` deklariert durch `[T]` und kann überall in der Klasse genutzt werden
# der Rest ist genau gleich, das einzige was sich eben geändert hat ist dass man dieses `TypeVar` nicht mehr braucht
# sondern direkt in der Deklaration von Klassen/Funktionen/Methoden die generischen Typvariabeln mit deklariert
@dataclass
class NewMyList[T]:
def __post_init__(self, ):
self.__internal_list: list[T] = field(default_factory=list)
self.__length: int = 0
def append(self, value: T):
self.__internal_list += [value]
self.__length += 1
def remove(self, value: T):
if value in self.__internal_list:
self.__internal_list.remove(value)
def get(self, i: int) -> Optional[T]:
if i < len(self):
return self[i]
return None
def __len__(self) -> int:
return self.__length
def __getitem__(self, i: int) -> T:
return self.__internal_list[i]
```

View File

@ -1,68 +0,0 @@
from typing import Generic, TypeVar, Optional, Union
from dataclasses import dataclass, field
T = TypeVar('T')
@dataclass
class MyList(Generic[T]):
def __post_init__(self, ):
self.__internal_list: list[T] = field(default_factory=list)
self.__length: int = 0
def append(self, value: T):
self.__internal_list += [value]
self.__length += 1
def remove(self, value: T):
if value in self.__internal_list:
self.__internal_list.remove(value)
def get(self, i: int) -> Optional[T]:
if i < len(self):
return self[i]
return None
def __len__(self) -> int:
return self.__length
def __getitem__(self, i: int) -> T:
return self.__internal_list[i]
@dataclass
class MyListNew[T]:
def __post_init__(self, ):
self.__internal_list: list[T] = field(default_factory=list)
self.__length: int = 0
def append(self, value: T):
self.__internal_list += [value]
self.__length += 1
def remove(self, value: T):
if value in self.__internal_list:
self.__internal_list.remove(value)
def get(self, i: int) -> Optional[T]:
if i < len(self):
return self[i]
return None
def __len__(self) -> int:
return self.__length
def __getitem__(self, i: int) -> T:
return self.__internal_list[i]
RealNumber = Union[int, float]
def add(x: RealNumber, y: RealNumber) -> RealNumber:
return x + y
type NewRealNumber = int | float
# sonst sieht alles gleich aus
def new_add(x: NewRealNumber, y: NewRealNumber) -> NewRealNumber:
return x + y

View File

@ -1,99 +1,286 @@
# Tutorium 01 - 20.10.2023
---
marp: true
paginate: true
theme: rose-pine
footer: EidP 2024 - Nils Pukropp - https://git.narl.io/nvrl/eidp-2024
style: ".columns {\r display: grid;\r grid-template-columns: repeat(2, minmax(0, 1fr));\r gap: 1rem;\r }"
---
# Tutorium 01 - 2024-10-17
Vorstellen, Orga, Zusammenfassung, Installation von WSL/VS Code
## Today
---
# Über mich
* Vorstellen
* Zusammenfassung Vorlesung
* Übungsblatt 01
* Installieren der benötigten Software
<div class="columns">
<div>
## About me
* Nils Pukropp
* 3 Semester Informatik B.Sc.
* [nils@narl.io](mailto:nils@narl.io)
* Discord: [.narl](https://discord.com/users/208979474988007425)
* Telegram: [@narl_np](https://t.me/narl_np)
* [Feedback](https://s.narl.io/s/Feedback-Tutorium-01)
<img src="../../src/img/mailto.png" height="200">
<img src="../../src/img/discord.png" height="200">
<img src="../../src/img/telegram.png" height="200">
<img src="../../src/img/feedback-google-forms.png" height="200">
<img src="./src/tutorium-01.png" height="200">
* Informatik B.Sc. 5. Semester
* EidP Tutor seit zwei Semestern
* Gerne einfach "Du"
## Zusammenfassung Vorlesung
### Orga
* 14 Blätter jeden Dienstag auf der [Homepage](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/)
* Abgabe im [Git](https://git.laurel.informatik.uni-freiburg.de/), Montags 9:00
* Ihr braucht 50% der 326 Punkte
* 2x Vorrechnen
* Muss nicht korrekt sein
* Wollen sehen dass ihr eure Aufgaben selber bearbeitet
* Ich werde euch fragen, wenn ihr gute Abgaben hattet
* Punkte für Anwesenheit im Tutorat
* Anwesenheit über [QR-Code](https://auth.laurel.informatik.uni-freiburg.de/) (ab nächste Woche)
### Python-Shell
* Python-Shell bietet einen interaktiven Modus
* Schnell Programme testen/kleinere Programme schreiben
### Zahlen
* `int` (Ganzzahlen)
* `float` (Kommazahlen)
* Grunderechenarten `+`, `-`, `*`, `/`, `//`
* Potenz `**`
* Modulo `%`
* `float` -> `int` (möglicher) Informationsverlust
* `int` -> `float` kein Informationsverlust
* `float` sind etwas komplizierter als `int`
</div>
<div>
```py
>>> 0.1 + 0.1 + 0.1
?
print("Hello", "Tutorium", "2024!")
>>> "Hello Tutorium 2024!"
```
### Git
</div>
</div>
* Git dient der Versionskontrolle
* Wir benutzen [Gitea](https://github.com/go-gitea/gitea) als Git-Service
* Remote Repositories
* Weboberfläche zum offnen im Browser
* Quasi eine Cloud für Code
* Weitere Services wie Github, Gitlab, ...
---
### Git - the nerdy way
# Wie wird das Tutorium ablaufen?
#### Vorteile
* Vorstellen des letzten Blatt
* Wichtiges aus Vorlesung/für nächstes Blatt
* Fragen zum nächsten Blatt
* Allgemeine Fragen zur Vorlesung
* Am Ende QR-Code für Anwesenheit
* Ihr könnt mir gerne am Ende privat noch Fragen über die Vorlesung oder das Studium stellen
[Anleitung von Dani](https://git.danielmironov.dev/mironov/eidp-tutorat)
---
* Man lernt umgang mit dem Terminal
* Es ist deutlich schneller nach Eingewöhnung
* Etwas unintuitiv wenn man sich mit dem Terminal nicht auskennt
# Kontakt
## [Übungsblatt 01](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet01.pdf)
- Mail: [nils@narl.io](mailto:)
- Tutorium-Files: [git.narl.io/nvrl/eidp-2024](https://git.narl.io/nvrl/eidp-2024)
- Telegram: [@narl_np](https://t.me/narl_np)
* Abgabe 23.10.2023
---
## Notes Tutorium
# Orga
Was müsst ihr machen/wissen
Die Windows `C:` Festplatte findet man in WSL unter `/mnt/c/`, andere Festplatten findet man analog dazu
---
`D: -> /mnt/d/`
...
# Studienleistung bekommen
## Wichtige Links
* Im HisInOne sich für dieses (oder ein anderes) Tutorium anmelden + Studienleistung (wird noch freigeschaltet)
* 14 Blätter, jeden Dienstag auf der [EidP Website](https://proglang.github.io/teaching/24ws/eidp.html)
* 2x Vorrechnen
* Muss nicht korrekt sein
* Ich werde gute Abgaben fragen
* 3 Punkte für Anwesenheit
* Ab nächstem Tutorium
* Über [QR-Code](https://auth.laurel.informatik.uni-freiburg.de/)
* Ihr braucht insgesamt mehr als 50% der Punkte (>50%)
* [Homepage EidP](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/)
* [EidP-Git (Abgaben)](https://git.laurel.informatik.uni-freiburg.de/)
* [QR-Code für Anwesenheit](https://auth.laurel.informatik.uni-freiburg.de/)
* [Mein Git](https://git.narl.io/nvrl/eidp-2024)
* [nils@narl.io](mailto:nils@narl.io)
* [Discord](https://discord.com/users/208979474988007425)
* [Telegram](https://t.me/narl_np)
* [Feedback](https://s.narl.io/s/Feedback-Tutorium-01)
---
# Prüfung
- Nach den Vorlesungen (Ende Februar/März)
- Setzt die Studienleistung vorraus
- Schwerere/Schwerste Klausur im ersten Semester
---
# Warum fällt vielen EidP so schwer?
- Zeitdruck in der Klausur
- In der Studienleistung schummeln ist einfach, in der Klausur nicht
- fehlende Übung im Programmieren
- Nicht ausreichend mit den Themen befasst
- Wahrscheinlich die Übungsblätter nicht vollständig selbst bearbeitet
- Nachdem man die Studienleistung hat (>50% Punkte) aufgehört sich mit der Vorlesung zu beschäftigen
---
# ChatGPT
- Wir tolerieren den Gebrauch
- **ABER**: Ihr müsst in der Klausur alles verstanden haben und genug Übung im Umgang mit Python haben
- Ob ihr jetzt Stunden braucht um die Lösung von ChatGPT nachzuvollziehen oder euch in Stunden die Lösung selbst erarbeitet und dabei praktische Programmiererfahrung sammelt ist eure Entscheidung.
- Aus eigenem Interesse würde ich generierte Stellen mit einem Kommentar versehen, damit ich einen Überblick habe welche Aufgaben schwerer fallen und wie viel ChatGPT verwendet wird
---
# Plagiat
- Wir tolerieren **kein** Plagiat
- Nicht von anderen Abschreiben
- Keine Lösungen aus dem Internet
- Erster Plagiat: 0 Punkte
- Zweiter Plagiat: Viel Erfolg im nächsten Jahr
---
# Abschließend
- Stellt Fragen im Tutorium
- Im Tutorium für alle
- Nach dem Tutorium für eine genauere Erklärung
- Nutzt die Studienleistung zum Üben
- Vor der Klausur werde ich wieder ein Zusatz-Tutorium veranstalten um euch Abschließend auf die Klausur vorzubereiten
- Gerne auch Fragen übers Studieren/Studium
---
# Zusammenfassung der Vorlesung
Was müsst ihr Wissen?
---
# Was muss ich installieren?
- [Setup Guide](https://proglang.github.io/teaching/24ws/eidp/setup.html)
- Zusammengefasst:
- WSL/Linux
- Python 3.12.x
- VS Code
- VS Code-Extensions:
- Python, Pylance, Flake8, autopep8
- Git
---
# Warum Linux statt WSL?
- Ihr werdet euch im Studium sowieso mit Linux auseinandersetzen
- Endlich kein Windows mehr
- Wir haben nicht einen Tutor der Windows benutzt
- Deutlich kürzere Installation auch in zukünftigen Vorlesungen
- Ihr lernt den Umgang mit der Shell deutlich schneller
---
# Was ist die Shell?
- Die Shell ermöglicht wie die UI mit dem Betriebssystem zu interagieren
- Ermöglicht einen präziseren Umgang mit eurem Betriebssystem
- Ungewohnt am Anfang, aber mit etwas Übung deutlich effektiver/schneller als auf UIs angewiesen zu sein
- Viele Prozesse sind nur mit der Shell möglich (haben kein UI)
---
# Was ist Git?
- Git dient der Versionskontrolle
- Ihr könnt neue/veränderte Datein zu Git hinzufügen um so eine Version einer Datei abzuspeichern
- Hat noch viele weiter Funktionen für die Software Entwicklung
- Für die Vorlesung braucht ihr lediglich `add`, `commit`, `pull`, `push`, (`status`)
---
<div class='columns'>
<div>
# `git status`
- Zeigt neue/veränderte/staged Datein an
</div>
<div>
# `git add`
- Fügt neue/veränderte Datein in den `staged` Bereich von Git
- Diese Datein sind noch nicht vollständig gespeichert
- Mit `git status` könnt ihr sehen welche bereits `staged` sind
</div>
</div>
---
# `git commit -m "commit message"`
- "Speichert" die Dateien, welche mit `git add` hinzugefügt wurden
---
# Git-Server
- Die Dateien sind jetzt aber nur lokal gespeichert
- Git-Server ermöglichen den Austausch von Quellcode im Internet
- So können auch mehrere an einem Quellcode arbeiten
---
<div class='columns'>
<div>
# `git pull`
- Der Git-Server wird nach neuen Änderungen gefragt
- Die neuen Änderungen werden heruntergeladen
</div>
<div>
# `git push`
- Lokal gespeicherte Änderungen werden auf den Git-Server hochgeladen
- Davor muss `git commit` verwendet worden sein
</div>
</div>
---
<div class=columns>
<div>
# Python
- Wird in `.py` Datein geschrieben
- Wird mit dem Befehl `python filename.py` ausgeführt
- Python übersetzt die Datei, und führt diese Zeile für Zeile aus
</div>
<div>
# Python Shell
- Interaktive Shell (ähnlich wie die Linux Shell)
- wartet auf Benutzereingaben
- interpretiert Benutzereingaben nacheinander
- Wird mit dem Befehl `python` ausgeführt
</div>
</div>
---
# Zahlentypen
<div class='columns'>
<div>
## `int`
- Ganzzahlen $\mathbb{Z}$
- In Python nur durch verfügbaren Speicher limitiert
</div>
<div>
## `float`
- Rationale Zahlen $\mathbb{R}$
- neben Speicherlimitierung auch noch limitiert in der Genauigkeit (Floatpoint Precision)
</div>
</div>
---
# Rechenoperation
- Wie mans kennt eigentlich
- Addition `+`
- Subtraktion `-`
- Multiplikation `*`
- Division `/`
- Ganzzahlige Division `//`
- Potenzen `**`
- Modulo `%`
---
# Übungsblatt 1 + Installationsprobleme

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

View File

@ -1,140 +0,0 @@
# Zusammenfassung der Vorlesung
## Variablen und Zuweisungen
```python
variable = 42
print(variable)
print(variable + variable)
```
- Case-Sensitive
- A-z, 0-9, _
- Keine Schlüsselwörte
### Kurzzuweisung
Operationen und Zuweisung können in einem Schritt durchgeführt werden mit `${Operation}=`
```python
number = 42
number **= 42
assert number == 42 ** 42
number += 42
assert number == 42 ** 42 + 42
```
---
## Assert
- Erwartet einen wahren Ausdruck, wenn dieser nicht erfüllt ist gibt es einen Fehler
- Gut um etwas schnell zu testen
```py
number = 42
assert number == 42
bigger_number = number ** number
assert number ** number == bigger_number
```
---
## Typen
```py
number = 42
string = '42'
assert number != string
```
---
## Funktionen
### Standardfunktionen
- Typen konvertieren
```python
>>> int(2.6)
2
>>> float(2)
2.0
>>> str(2.0)
'2.0'
>>> type(2.0)
<class 'float'>
```
- Input/Output
```python
>>> input("Hier Input geben: ")
Hier Input geben: 42
'42'
>>> print(42)
42
```
### Funktionen kombinieren
```python
>>> int(str(2))
2
>>> str(int('2'))
'2'
```
### Neue Funktionen definieren
Mit `def` können neue Funktionen definiert werden
```python
def my_print_func(some_text):
print(some_text)
def my_add(a, b):
return a + b
my_print_func(my_add(1, 2)) # prints 3
```
---
## Imports
Mit `import` können Module (andere Python-Datein) importiert und benutzt werden
```python
import math
print(math.cos(math.pi)) # prints -1.0
```
Mit `from` und `import` können Sachen aus einem Modul spezifisch importiert werden
```python
from math import cos, pi
print(cos(pi)) # prints -1.0
```
---
## Scopes
Variabeln existieren in so genannten Scopes, am besten veranschaulicht man sich das einfach:
```python
GLOBAL_X = 42
def my_func():
x_in_func = 42 # x_in_func wird hier im Scope der Funktion erstellt
print(GLOBAL_X) # GLOBAL_X ist im globalen Scope,
# also auch hier im Funktions-Scope, weil dieser Scope auch im globalen Scope ist
print(x_in_func) # wirft einen Fehler, weil x_in_func nicht im Scope ist
print(GLOBAL_X) # Global Scope
```

View File

@ -1,57 +0,0 @@
# Korrektur Exercise-01
## Häufige Fehler
### Aufgabe 1.1
Wer weiterhin Probleme bei der Installation hat, bitte nach dem Tutorat
### Aufgabe 1.2
- Terminierung war nicht erfüllt für $x,y=0$ aber nicht $x = y$
- Dadurch auch kein Algorithmus laut Definition der Vorlesung, gab keinen Abzug wenn man alle Bedingungen als erfüllt ansah
### Aufgabe 1.3
- Es ging vor allem um den Unterschied zwischen der Python-Shell und dem normalen ausführen von `.py` Dateien
### Aufgabe 1.4
- Achtet auf die genau Anforderung!!!
```sh
Python
Python
Python
```
heißt nicht
```sh
Python
Python
Python
```
oder
```sh
Python
Python
Python
```
oder
```sh
Python
Python
Python
```
## Punkteverteilung
![image not found](./src/punkteverteilung.png)

View File

@ -1,5 +0,0 @@
# [Exercise-02](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet02.pdf)
- Abgabe 30.10.2023 9:00 Uhr
- Achtet auf den Build-Output (Linter, Notes, ...)
- Fragen?

View File

@ -1,212 +0,0 @@
# Beispiel Git-Workflow
---
## SSH-Key generieren und im Git hinzufügen
### Linux & Mac-OS
Generiere einen Key mit
```sh
ssh-keygen -t ed25519 -C "you@mail.com"
```
Gib den Key in die Konsole aus mit
```sh
cat ~/.ssh/id_ed25519.pub
```
Kopiere mit Strg+Shift+C oder Rechtklick den Key aus dem Terminal und fügen ihn im [Git](https://git.laurel.informatik.uni-freiburg.de/user/settings/keys) als SSH Key hinzu.
### Windows
Generiere einen Key und kopiere ihn
```ps
ssh-keygen.exe -t ed25519 -C "you@mail.com"
cat ~/.ssh/id_ed25519.pub | clip
```
fügen dann den Key im [Git](https://git.laurel.informatik.uni-freiburg.de/user/settings/keys) als SSH Key hinzu.
### Mac
Generiere einen Key mit
```sh
ssh-keygen -t ed25519 -C "you@mail.com"
pbcopy < ~/.ssh/id_ed25519.pub
```
fügen dann den Key im [Git](https://git.laurel.informatik.uni-freiburg.de/user/settings/keys) als SSH Key hinzu.
---
## Das Repository clonen
Erstmal ist es wichtig wie man sich im Terminal überhaupt bewegt und umschaut. Wenn wir das Terminal starten, egal ob in Windows/Linux/Mac landen wir im Home-Verzeichnis often bezeichnet als `~`. Um den ersten Schritt zu gehen müssen wir erstmal wissen was wir hier überhaupt haben. Hierfür haben wir das Programm `ls`, welches den Inhalt in einem (ohne Argumente im aktuellen) Verzeichnis auflistet. Eine Beispielausgabe wäre:
```sh
nils@linux ~> ls
total 16
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Desktop/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:16 Downloads/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Pictures/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Videos/
```
Nun können wir uns in die anderen Verzeichnisse bewegen mit `cd` (change directory).
```sh
nils@linux ~> cd Downloads/
nils@linux ~/Downloads> ls
total 0
-rw-r--r-- 1 nils nils 0 Oct 27 02:19 cat.png
```
mit `cd ..` können wir uns jetzt ein Verzeichnis wieder nach oben bewegen
```sh
nils@linux ~/Downloads> cd ..
nils@linux ~> ls
total 16
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Desktop/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:19 Downloads/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Pictures/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Videos/
```
nun clonen wir das Repository indem wir ins [Git](https://git.laurel.informatik.uni-freiburg.de/2021WS-EiP/) gehen, auf unser persönliches Repository gehen. Und oben bei **SSH** auf **Copy**/**Kopieren** gehen.
Nun müssen wir einfach nur noch folgenden Befehl eingeben
```sh
nils@linux ~> git clone ssh://git@git.laurel.informatik.uni-freiburg.de:2222/2021WS-EiP/np163.git
Cloning into 'np163'...
The authenticity of host '[git.laurel.informatik.uni-freiburg.de]:2222 ([132.230.166.132]:2222)' can't be established.
ED25519 key fingerprint is SHA256:zR3d+3MewcoiAuwVidHYfWcsNjT/OVz5FR6IwIyTNCs.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[git.laurel.informatik.uni-freiburg.de]:2222' (ED25519) to the list of known hosts.
remote: Enumerating objects: 594, done.
remote: Counting objects: 100% (594/594), done.
remote: Compressing objects: 100% (573/573), done.
remote: Total 594 (delta 336), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (594/594), 86.90 KiB | 2.63 MiB/s, done.
Resolving deltas: 100% (336/336), done.
```
nun können wir mit `ls` nachschauen dass ein neuer Ordner erschienen ist, in meinem Fall **np163**.
```sh
nils@linux ~> ls
total 20
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Desktop/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:19 Downloads/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Pictures/
drwxr-xr-x 2 nils nils 4096 Oct 27 02:14 Videos/
drwxr-xr-x 17 nils nils 4096 Oct 27 02:24 np163/
```
Nun können wir diesen Ordner in VSCode öffnen und haben einen Workspace um die Übungsaufgaben zu bearbeiten.
---
Nun bewegen wir uns ins Git-Verzeichnis mit `cd np163`. Und führen unseren ersten Git-Command aus `git status`
```sh
nils@linux ~/np163 (master)> git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
```
wir sehen, dass aktuell noch nichts im Verzeichnis geändert wurde. Das ändern wir jetzt indem wir in VSCode eine `hello_world.py` erstellen. Und den `git status` wiederholen
```sh
nils@linux ~/np163 (master)> git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello_world.py
nothing added to commit but untracked files present (use "git add" to track)
```
hier schlägt uns Git auch direkt schon vor `git add` zu verwenden um die neue Datei hinzuzufügen.
```sh
nils@linux ~/np163 (master)> git add hello_world.py
nils@linux ~/np163 (master)> git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: hello_world.py
```
mit `git add -A` können alle aktuelle Änderungen hinzugefügt werden.
nun können wir die Datei in unser Git eintragen indem wir `git commit -m 'meine nachricht'` verwenden.
```sh
nils@linux ~/np163 (master)> git commit -m 'created hello_world.py'
[master 4191d5b] created hello_world.py
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 hello_world.py
```
und diese Änderung dann mit `git push` hochladen.
```sh
nils@linux ~/np163 (master)> git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 285 bytes | 285.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://git.laurel.informatik.uni-freiburg.de:2222/2021WS-EiP/np163.git
06b6eb7..4191d5b master -> master
```
---
## Git - VSCode
Zunächst erstellen wir eine Datei mit dem Namen `hello_world.py` über die Verzeichnisverwaltung von VSCode. Wir sehen dass die neue Datei bereits grün angezeigt, weil es eine neue Datei im Git ist.
![image not found](src/vscode-01.png)
Nun wechsel wir die Ansicht von der Verzeichnisverwaltung zu Git ganz links außen.
![image not found](src/vscode-02.png)
Nun drücken wir bei unserer neu erstellten Datei auf das `+`. Diese wird dann als `Staged Changes` angezeigt.
![image not found](src/vscode-03.png)
Nun legen wir eine Nachricht fest welche die Änderungen representiert und beschreibt.
![image not found](src/vscode-04.png)
Nun drücken wir auf `Commit` und nun sind unsere Änderungen übernommen.
![image not found](src/vscode-05.png)
Nun können wir noch auf `Sync Changes` drücken um die Änderungen auch an den Git-Services **Gitea** zu schicken. Danach werden keine weiteren Dateien mehr im Git-Reiter angezeigt und online sehen wir dass unsere Änderungen hochgeladen wurden.
![image not found](src/vscode-06.png)
![image not found](src/vscode-07.png)
---
Für Anregung gerne eine kurze [Mail](mailto:nils@narl.io) schreiben.

View File

@ -1,21 +0,0 @@
# Tutorium 02 - 27.10.2023
## Today
- [Korrektur Exercise-01](./CORRECTION.md)
- [Zusammenfassung Vorlesung](./COMPREHENSION.md)
- [Exercise-02](./EXERCISE-02.md)
- [Beispiel Git-Workflow](./GIT.md)
## About me
- [nils@narl.io](mailto:nils@narl.io)
- Discord: [.narl](https://discord.com/users/208979474988007425)
- Telegram: [@narl_np](https://t.me/narl_np)
- [Feedback](https://s.narl.io/s/Feedback-Tutorium-01)
<img src="../../src/img/mailto.png" height="200">
<img src="../../src/img/discord.png" height="200">
<img src="../../src/img/telegram.png" height="200">
<img src="../../src/img/feedback-google-forms.png" height="200">
<img src="./src/tutorium-02.png" height="200">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1 +0,0 @@
print("hello world")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,65 +0,0 @@
# Tutorium 04 - 10.11.2023
## [Exercise 02](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet02.pdf) und [Exercise 03](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet03.pdf)
### Punkteverteilung Exercise 02
![image not found](points_ex2.png)
### Punkteverteilung Exercise 03
![image not found](points_ex3.png)
### Häufige Fehler
- **Schaut genau was muss ausgegeben werden!!!**
- Achtet auf den Build-Output
- **Linter-Error?** (-0.5 Punkte pro Datei)
- **Syntax-Error?** (0 Punkte ab Exercise 4)
- **Stunden eingetragen?** (-0.5 Punkte)
- lest euch die Aufgaben genau durch
- kommentiert keinen Quellcode aus, lasst ihn weg, oder lasst ihn stehen
- Testet euren Code mit `assert`
- später lernen wir noch bessere Tests kennen
- lasst eure `assert` nicht einfach in der Logik stehen!
```py
def some_function(arg):
assert arg <= 360 # WRONG!
return calculate(arg)
```
```py
def some_function(arg) -> float:
return calculate(arg)
if __name__ == "__main__":
# Right! Nur testen ob alles tut, mehr nicht
# und in __main__ packen, damit nicht jeder import die asserts aufruft
assert some_function(0.69) <= 42
assert some_function(0.420) <= 1337
```
## Vorstellen/Vorrechnen
- mz242
- vb205
## [Exercise 04](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet04.pdf)
- Abgabe Montag 9:00 im [Git](https://git.laurel.informatik.uni-freiburg.de/)
- Typannotationen jetzt wichtig.
- `min` Funktion darf in 4.1 nicht benutzt werden.
- Aufgabenteile werden mit **0 Punkten bewertet**, wenn:
- Dateien und Funktionen nicht so benannt sind, wie im Aufgabentext gefordert;
- Dateien falsche Formate haben, z.B. PDF statt plaintext;
- Pythonskripte wegen eines Syntaxfehlers nicht ausführbar sind.
- Verwenden Sie nur Befehle und Programmiertechniken, die Inhalt der bisherigen Vorlesungen (bis zum Abgabetermin) und Übungsblättern waren. (Ausnahme: f-Strings)
- Fragen?
# Nachkorrektur
- mw793
- fk439 (Zeitangabe)
- jb1484 (Verspätete Akinator)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,125 +0,0 @@
# Tutorium 05 - 17.11.2023
## Korrektur [Exercise-04](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet04.pdf)
### Punkteverteilung
![img not found](./img/pointdistribution_exercise04.png)
### Häufige Fehler
- Type Annotations
- Print-Statements, Top-Level Statements in Logik/nicht in
```python
if __name__ == "__main__":
assert # some test
```
- Ich kann euch prinzipiell immer 0 Punkte geben wenn Ihr etwas verwendet, was nicht Teil der Vorlesung war
- Lest die Aufgabenstellungen/Hinweise auf dem Blatt
- Benennt eure Dateien/Methoden richtig
## Vorrechnen
1. `lists.py`
- a) `even`: no43
- b) `min`: cl393
- c) `max`: mt367
2. `euler.py`
- a) `fac`: au56
- b) `approx_e`: rw208
3. `binary.py`
- a) `to_num`: ua28
- b) `stream_to_nums`: md489
## [Exercise-05](https://proglang.informatik.uni-freiburg.de/teaching/info1/2023/exercise/sheet05.pdf)
- Abgabe Montag 09:00 Uhr im [git](https://git.laurel.informatik.uni-freiburg.de/)
- Probleme beim installieren von `pygame`?
## Übungsaufgaben
### [Primes](./src/primes.py)
Schreibe eine Funktion `prime_factorization` die eine Ganzzahl `n` entgegen nimmt und alle Primfaktoren berrechnet und die gegebene Zahl `n` in einen Paar mit den Primfaktoren als Liste zurückgibt. Denkt dabei an die richtigen Type Annotations
```python
def prime_factorization(n):
pass
```
### [Dataclass](./src/data_classes.py)
Schreiben Sie eine Datenklasse `Fraction` (Bruch), beachten Sie dabei die Type Annotations. Ein Bruch besteht aus einem `divident` und einem `divisor`.
```python
from dataclasses import dataclass
@dataclass
class Fraction:
pass
```
Nun modellieren wir Hilfsmethoden für unsere Datenklassen, die uns später bei der Logik von Brüchen helfen
```python
# the greatest common divisor of two numbers `a`, `b`
def gcd(a, b):
pass
# this shortens a fraction to its most reduced representation
def shorten_fraction(fraction):
pass
```
Abschließend modellieren wir nun auch noch das Verhalten von Brüchen indem wir Methoden direkt in der Datenklasse erstellen. Type Annotations!
```python
# Multiplication of two fractions
# `Fraction(1 / 2) * Fraction(2 / 6) -> Fraction(1, 6)`
# Extra: make it possible to multiply `int` with a fraction
# `Fraction(1 / 2) * 2 -> Fraction(1 / 4)`
def __mul__(self, o):
pass
# The division of two fraction
# `Fraction(1 / 2) / Fraction(2 / 6) -> Fraction(3, 2)`
# Extra: make it possible to divide `int` with a fraction
# `Fraction(1 / 4) / 2 -> Fraction(1 / 2)`
def __truediv__(self, o):
pass
# The negative of a fraction
# `-Fraction(1 / 2) -> Fraction(-1 / 2)`
def __neg__(self):
pass
# The addition of two fractions
# `Fraction(1 / 4) + Fraction(2 / 8) -> Fraction(1 / 2)`
# Extra: make it possible to add `int` with a fraction
# `Fraction(1 / 4) + 1 -> Fraction(5 / 4)`
def __add__(self, o):
pass
# The subtraction of two fractions
# `Fraction(1 / 2) - Fraction(1 / 4) -> Fraction(1 / 4)`
# Extra: make it possible to subtract `int` with a fraction
# `Fraction(5 / 2) - 1 -> Fraction(3 / 2)`
def __sub__(self, o):
pass
# The `equal`-function is == and should only care about reduced fractions
# `Fraction(1 / 2) == Fraction(2 / 4)` is True
def __eq__(self, o):
pass
# The `not equal`-function is != and should only care about reduced fractions exactly as equal
def __neq__(self, o):
pass
# The str function should return this string `(divident / divisor)`
def __str__(self):
pass
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,88 +0,0 @@
from dataclasses import dataclass
def gcd(a: int, b: int) -> int:
x = abs(a)
y = abs(b)
while (y):
x, y = y, x % y
return x
def shorten_fraction(fraction: 'Fraction') -> 'Fraction':
g: int = gcd(fraction.divident, fraction.divisor)
return Fraction(fraction.divident // g, fraction.divisor // g)
@dataclass
class Fraction:
divident: int
divisor: int
def __neg__(self: 'Fraction') -> 'Fraction':
return -1 * self
def __mul__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
if isinstance(o, int):
o = Fraction(o, 1)
return shorten_fraction(Fraction(self.divident * o.divident,
self.divisor * self.divisor))
def __rmul__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
return self * o
def __truediv__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
if isinstance(o, int):
o = Fraction(o, 1)
return shorten_fraction(Fraction(self.divident * o.divisor,
self.divisor * o.divident))
def __rtruediv___(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
return self / o
def __add__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
if isinstance(o, int):
o = Fraction(o, 1)
g: int = gcd(self.divisor, o.divisor)
l: int = abs(self.divisor * o.divisor) // g
return shorten_fraction(Fraction(self.divident
* (l // self.divisor)
+ o.divident
* (l // o.divisor), l))
def __radd__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
return self + o
def __sub__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
if isinstance(o, int):
o = Fraction(o, 1)
return self + -o
def __rsub__(self: 'Fraction', o: 'Fraction | int') -> 'Fraction':
return self - o
def __eq__(self: 'Fraction', o: 'Fraction | int') -> bool:
if isinstance(o, int):
o = Fraction(o, 1)
shorten_self: 'Fraction' = shorten_fraction(self)
shorten_o: 'Fraction' = shorten_fraction(o)
return (shorten_self.divident == shorten_o.divident
and shorten_self.divisor == shorten_o.divisor)
def __neq__(self: 'Fraction', o: 'Fraction | int') -> bool:
return not (self == o)
def __str__(self: 'Fraction'):
return f"({self.divident} / {self.divisor})"
if __name__ == "__main__":
assert Fraction(1, 1) == 1
assert Fraction(1, 2) == (out := Fraction(2, 4) /
Fraction(g := gcd(2, 4), g)), f"!= {out}"
assert (sol := Fraction(9, 20)) == (
res := Fraction(1, 5) + Fraction(1, 4)), f"!= {out}"
assert (sol := Fraction(-9, 20)) == (
res := Fraction(1, -5) + Fraction(-1, 4)), f"!= {out}"
assert (sol := Fraction(-1, 20)) == (
res := Fraction(1, 5) + Fraction(-1, 4)), f"!= {out}"

View File

@ -1,34 +0,0 @@
def prime_factorization(num: int) -> tuple[int, list[int]]:
# our list of primefactors
primefactors: list[int] = []
# our first prime number
prime: int = 2
# we don't want to modify our number and copy it to `n`
n: int = num
# iterate until we find the last step the square of
# our prime is bigger than our rest number
while n != 1:
# if our number is dividable by our prime
if n % prime == 0:
# we can add the prime to our primefactors
primefactors.append(prime)
# and divide our rest number by the prime number
n //= prime
else:
# increment until next prime number
prime += 1
# finally return our tuple with our number and primefactors
return (num, primefactors)
if __name__ == "__main__":
assert (sol := (100, [2, 2, 5, 5])) == (
res := prime_factorization(100)), f"{res} is not {sol}"
assert (sol := (69, [3, 23])) == (
res := prime_factorization(69)), f"{res} is not {sol}"
assert (sol := (31, [31])) == (
res := prime_factorization(31)), f"{res} is not {sol}"
assert (sol := (123490823022, [2, 3, 3, 3, 3, 7, 7, 7, 1123, 1979])) == (
res := prime_factorization(123490823022)), f"{res} is not {sol}"

View File

@ -1,68 +0,0 @@
from dataclasses import dataclass
# the greatest common divisor of two numbers `a`, `b`
def gcd(a, b):
pass
# this shortens a fraction to its most reduced representation
def shorten_fraction(fraction):
pass
@dataclass
class Fraction:
# Multiplication of two fractions
# `Fraction(1 / 2) * Fraction(2 / 6) -> Fraction(1, 6)`
# Extra: make it possible to multiply `int` with a fraction
# `Fraction(1 / 2) * 2 -> Fraction(1 / 4)`
def __mul__(self, o):
pass
# The division of two fraction
# `Fraction(1 / 2) / Fraction(2 / 6) -> Fraction(3, 2)`
# Extra: make it possible to divide `int` with a fraction
# `Fraction(1 / 4) / 2 -> Fraction(1 / 2)`
def __truediv__(self, o):
pass
# The negative of a fraction
# `-Fraction(1 / 2) -> Fraction(-1 / 2)`
def __neg__(self):
pass
# The addition of two fractions
# `Fraction(1 / 4) + Fraction(2 / 8) -> Fraction(1 / 2)`
# Extra: make it possible to add `int` with a fraction
# `Fraction(1 / 4) + 1 -> Fraction(5 / 4)`
def __add__(self, o):
pass
# The subtraction of two fractions
# `Fraction(1 / 2) - Fraction(1 / 4) -> Fraction(1 / 4)`
# Extra: make it possible to subtract `int` with a fraction
# `Fraction(5 / 2) - 1 -> Fraction(3 / 2)`
def __sub__(self, o):
pass
# The `equal`-function is == and should only care about reduced fractions
# `Fraction(1 / 2) == Fraction(2 / 4)` is True
def __eq__(self, o):
pass
# The `not equal`-function is != and should only care about reduced fractions exactly as equal
def __neq__(self, o):
pass
# The str function should return this string `(divident / divisor)`
def __str__(self):
pass

View File

@ -1,285 +0,0 @@
# 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
<details>
<summary>QR-Code:</summary>
<img src="../../src/img/discord-invite.png" height=800>
</details>
## Korrektur Blatt 05
- am Samstag, ich hab mich etwas vertan bei der Korrektur
- Punkteverteilung und häufige Fehler werden hier hinzugefügt
![img not found](./img/points_ex05.png)
### Häufige Fehler
- Type annotation
- `@dataclass` nicht benutzt
- mutieren von erstellten Objekt
- Call über Class und nicht über Objekt
## Vorrechnen
1. Python-Game
1. `Vec2`: aw616
2. `add_vecs`: fk439
3. `Item`: ln200
4. `Snake`: lp321
5. `Game`: rl173
6. `turn_direction`: ih205
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<T: Add>(some_list: Vec<T>) -> T {
// ...
}
```
oder noch schöner
```rust
fn some_func<T>(some_list: Vec<T>) -> T
where T: Add<Output = T> + 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
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,131 +0,0 @@
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)

View File

@ -1,20 +0,0 @@
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()

View File

@ -1,39 +0,0 @@
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)

View File

@ -1,53 +0,0 @@
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"])

View File

@ -1,8 +0,0 @@
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]

View File

@ -1,193 +0,0 @@
# Tutorium 07 - 01.12.2023
## Execrise 06
![img not found](./img/punkteverteilung_exercise06.png)
- Korrektur wieder am Samstag
### Problematik ChatGPT und Plagiate
- ChatGPT ist ein tolles Tool, warum?
- Manchmal liefert es andere Lösungen zu Problemen
- Grundverständnis bei neuen Problemen
- integriert in die IDE (z.B. Github Copilot):
- schneller Code schreiben
<details>
<summary>Wie viele Zeilen Code schreibt ein Entwickler durchschnittlich am Tag?</summary>
<space><space><space><space>10 bis 50 Codezeilen
</details>
- Leichtsinnsfehler ausbessern
- Kurz: Es nimmt einen repetetive Arbeit ab
#### Die Problematik?
- Ein EidP soll das Grundverständnis von Programmieren vermittelt werden
- Denkweise
- Konzepte in der theoretischen Informatik
- Konzepte in Programmiersprachen
- Übung
- Um ChatGPT sinnvoll zu nutzen müsst ihr diese Grundverständnis bereits besitzen
- Auch Studierende mit Vorwissen profitieren davon die Übung sinnvoll zu bearbeiten
- Wenn Ihr für die Aufgaben ChatGPT verwendet, dann habt ihr nicht genug Vorwissen
<details>
<summary>Studienleistung WS2022</summary>
<img src="./img/ws2022-studienleistung.png" width=833 height=auto>
</details>
<details>
<summary>Notenverteilung WS2022</summary>
<img src="./img/ws2022-notenverteilung.png" width=833 height=auto>
</details>
#### Also, macht eure Aufgaben selber!
---
## [Advent of Code](https://adventofcode.com/)
- Aktuell gibt es wie jedes Jahr wieder [Advent of Code](https://adventofcode.com/).
- Jeden Tag eine neue Aufgabe
- Von einfach bis unmenschlich schwer (tendenziell eher einfach)
- insane Storyline
- [Tag 1](https://adventofcode.com/2023/day/1) ist schon da!
- Auch sehr nice um neue Sprachen zu lernen
- Persönliche Empfehlungen:
- [Rust](https://www.rust-lang.org/) - Multi-paradigm mit einzigartigen Konzepten
- [Haskell](https://www.haskell.org/) - Funktionale Programmierung
- [Go](https://go.dev/) - OOP, High-Level Programming, sehr einfach Projekte mit aufzusetzen!
- [Zig](https://ziglang.org/) - Low-Level Programming, rein prozedural, C-Alternative
----
## Wichtiges/Hilfreiches für Exercise-07
### Rekursion
- Rekursion in Python sind Funktionen die sich selbst aufrufen
```python
def fac(n: int) -> int:
if n <= 1: # Abbruchbedingung, kein Rekursiver Aufruf mehr!
return 1
return n * fac(n - 1) # Rekursiver Aufruf
```
- Eine Rekursion braucht eine **Abbruchbedingung**
- primitive Rekursionen können auch einfach iterative gelöst werden
```python
def fac2(n: int) -> int:
fac = 1
for i in range(1, n + 1):
fac *= i
return fac
```
- Eine Rekursion kann mehrere Rekursionspfade haben! (Kaskadenförmige Rekursion), welche auch primitiv berechenbar sind!
```python
def fib(n: int) -> int:
if n in {0, 1}: # Abbruchbedingung
return n
return fib(n - 1) + fib(n - 2) # mehrere Rekursionsaufrufe
```
- Wie Funktioniert das?
- Es wird ein Rekursionsbaum aufgebaut
- Wenn dieser Fertig ist wird berechnet
- Z.b. `fac`:
```
fac(5)
5 * fac(4)
5 * 4 * fac(3)
5 * 4 * 3 * fac(2)
5 * 4 * 3 * 2 * fac(1)
5 * 4 * 3 * 2 * 1
120
```
```
fib(4)
fib(3) + fib(2)
(fib(2) + fib(1)) + (fib(0) + fib(1))
((fib(0) + fib(1)) + fib(1)) + (fib(0) + fib(1))
((0 + 1) + 1) + (0 + 1)
3
```
- Gibt es Rekursionen die nicht iterative berechenbar sind?
- $\mu$-Rekursionen oder partiell Rekursionen
- erste partiell rekursive Funktion von Wilhelm Ackermann 1926, die "Ackermannfunktion"
$\alpha(0, m) = m + 1$ \
$\alpha(n, 0) = \alpha(n - 1, 1)$ \
$\alpha(n, m) = \alpha(n, \alpha(n, m - 1))$
```python
def ack(n: int, m: int) -> int:
match (n, m):
case (0, _):
return m + 1
case (_, 0):
return ack(n - 1, 1)
case _:
return ack(n - 1, ack(n, m - 1))
```
#### Tipp:
Man kann alles rekursiv Aufbauen mit Operatoren (`+, -, *, /, %, //, &&, and, ...`), also auch Listen oder Strings
```python
def all_fac(max: int) -> list[tuple[int, int]]:
if max == 0: # Abbruchbedingung
return [(0, 1)]
return [(max, fac(max))] + all_fac(max - 1) # Rekursion
def all_fac_str(min: int, max: int) -> str:
if min >= max: # Abbruchbedingung
return f"{fac(min)}"
return f"{fac(min)} " + all_fac_str(min + 1, max) # Rekursion
def fib_str(n: int) -> str:
if n in {0, 1}: # Abbruchbedingung
return str(n)
return f"({fib_str(n - 1)} + {fib_str(n - 2)})" # Rekursion
```
---
### Rekursion in Bäumen
- Drei möglichkeiten einen Baum *abzulaufen*
- **Pre-Order**: Knoten, links, rechts
```python
def preorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
print(value)
preorder(left)
preorder(right)
case _:
return
```
- **Post-Order**: links, rechts, Knoten
```python
def postorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
postorder(left)
postorder(right)
print(value)
case _:
return
```
- **In-Order**: links, Knoten, rechts
```python
def inorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
inorder(left)
print(value)
inorder(right)
case _:
return
```
---
## Fragen?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,57 +0,0 @@
import random
def remove_char(string: str, c: str) -> str:
for i, s in enumerate(string):
if s == c:
return string[:i] + string[i + 1:]
return string
def perfect_chars(inp: str, sol: str) -> str:
chars = ""
for c, s in zip(inp, sol):
if c == s:
chars = chars + c
return chars
def correct_chars(inp: str, sol: str) -> str:
res = ""
for c in inp:
if c in sol:
res += c
sol = remove_char(sol, c)
return res
def compare(inp: str, sol: str) -> tuple[int, int]:
perfect = len(perfect_chars(inp, sol))
correct = len(correct_chars(inp, sol))
return perfect, correct - perfect
def compare_alt(inp: str, sol: str) -> tuple[int, int]:
perfect = perfect_chars(inp, sol)
for c in perfect:
sol = remove_char(sol, c)
inp = remove_char(inp, c)
correct = correct_chars(inp, sol)
return len(perfect), len(correct)
def game(length: int, symbols: str):
solution = "".join(random.choices(symbols, k=length))
print("Länge:", length, "Zeichen:", symbols)
result = (0, 0)
while result != (length, 0):
c = input()
if len(c) != length:
print("Try again!")
continue
result = compare(c, solution)
print("Antwort:", result[0] * "X" + result[1] * "-")
if __name__ == '__main__':
game(5, "ABCDE")

View File

@ -1,35 +0,0 @@
from typing import Optional
def head[T](xs: list[T]) -> Optional[T]:
if not xs:
return None
return xs[0]
def tail[T](xs: list[T]) -> Optional[list[T]]:
if not xs:
return None
return xs[1:]
def concat[T](xss: list[list[T]]) -> list[T]:
outer = list()
for xs in xss:
outer += xs
return outer
def zip[T, U](xs: list[T], ys: list[U]) -> list[tuple[T, U]]:
out = list()
for i, x in enumerate(xs):
if i >= len(ys):
return out
else:
out += [(x, ys[i])]
return out
def assoc[T, U, V](t: tuple[tuple[T, U], V]) -> tuple[T, tuple[U, V]]:
(x, y), z = t
return (x, (y, z))

View File

@ -1,36 +0,0 @@
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]'] = None
right: Optional['Node[T]'] = None
type BTree[T] = Node[T] | None
def preorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
print(value)
preorder(left)
preorder(right)
case _:
return
def postorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
postorder(left)
postorder(right)
print(value)
case _:
return
def inorder[T](tree: BTree[T]):
match tree:
case Node(value, left, right):
inorder(left)
print(value)
inorder(right)
case _:
return

View File

@ -1,55 +0,0 @@
def fibonacci(n: int) -> int:
if n in {0, 1}: # Abbruchbedingung
return n
return fibonacci(n - 1) + fibonacci(n - 2) # mehrere Rekursionsaufrufe
def fac(n: int) -> int:
if n <= 0: # Abbruchbedingung, kein Rekursiver Aufruf mehr!
return 1
return n * fac(n - 1) # Rekursiver Aufruf
def fac2(n: int) -> int:
num = 1
for i in range(1, n + 1):
num *= i
return num
def ack(n: int, m: int) -> int:
match (n, m):
case (0, _):
return m + 1
case (_, 0):
return ack(n - 1, 1)
case _:
return ack(n - 1, ack(n, m - 1))
def all_fac(max: int) -> list[tuple[int, int]]:
if max == 0: # Abbruchbedingung
return [(0, 1)]
return [(max, fac(max))] + all_fac(max - 1) # Rekursion
def all_fac_str(min: int, max: int) -> str:
if min >= max: # Abbruchbedingung
return f"{fac(min)}"
return f"{fac(min)} " + all_fac_str(min + 1, max) # Rekursion
def fib_str(n: int) -> str:
if n in {0, 1}:
return str(n)
return f"({fib_str(n - 1)} + {fib_str(n - 2)})"
if __name__ == "__main__":
assert [fibonacci(n) for n in range(15)] == [0, 1, 1, 2,
3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
assert [fac(n) for n in range(11)] == [1, 1, 2, 6, 24,
120, 720, 5040, 40320, 362880, 3628800]
assert [fac(n) for n in range(10)] == [fac2(n) for n in range(10)]
assert list(reversed(all_fac(10))) == [(n, fac(n)) for n in range(11)]
assert all_fac_str(0, 10) == "1 1 2 6 24 120 720 5040 40320 362880 3628800"

View File

@ -1,589 +0,0 @@
---
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

Binary file not shown.

View File

@ -1,514 +0,0 @@
---
marp: true
paginate: true
class: invert
# theme: uncover
footer: Tutorium 09 - 16.12.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-09
header:
---
# Tutorium 09
Korrektur 08 - Vererbung, OOP, Datenkapselung
---
# Korrektur 08
---
# Punkteverteilung
![](img/pointdistribution_ex08.png)
---
# Häufige Fehler
* `@dataclass` nicht verwendet
* `__init__` überschrieben, obwohl `@dataclass` das macht und dann `super().__init__()` vergessen
* Kein Polymorphismus verwendet, also Code Duplikate oder auf `self` gematched/`isinstance()` verwendet
* Code nicht getestet, Datei nicht ausführbar => **0 Punkte**!
---
# Musterlösung - Aufgabe 8 a,b)
```py
import math
from dataclasses import dataclass
@dataclass
class Vec2:
x: float
y: float
def abs(self) -> float:
return math.sqrt(self.x * self.x + self.y * self.y)
```
---
# Musterlösung - Aufgabe 8 c)
```py
@dataclass
class GameObject:
position: Vec2
radius: int
alive: bool
color: tuple[int, int, int]
@dataclass
class Projectile(GameObject):
speed: float
@dataclass
class StaticObject(GameObject):
rotation: float
```
---
# Musterlösung - Aufgabe 8 c)
```py
class Item(StaticObject):
amount: int
class Ammunition(Item):
pass
class Health(Item):
pass
class Ship(GameObject):
shots: int
hp: int
class Asteroid(StaticObject):
special: bool
```
---
# Musterlösung - Aufgabe 8 d)
```python
class GameObject:
# ...
def update(self, width: int, height: int, delta: float):
if not (0 <= self.position.x < width and 0 <= self.position.y < height):
self.alive = False
class Projectile(GameObject):
speed: float
def update(self, width: int, height: int, delta: float):
self.position.y -= delta * self.speed
super().update(width, height, delta)
```
---
# Musterlösung - Aufgabe 8 d)
```python
class StaticObject(GameObject):
rotation: float
def update(self, width: int, height: int, delta: float):
self.position.y += delta
self.rotation += delta / self.radius
super().update(width, height, delta)
class Ship(GameObject):
shots: int
hp: int
def update(self, width: int, height: int, delta: float):
if self.hp <= 0:
self.hp = 0
self.alive = False
super().update(width, height, delta)
```
---
# Musterlösung - Aufgabe 8 e)
```python
@dataclass
class GameObject:
# ...
def is_colliding(self, other: "GameObject") -> bool:
dist = Vec2(self.position.x - other.position.x, self.position.y - other.position.y)
return dist.abs() <= self.radius + other.radius
```
---
# Musterlösung - Aufgabe 8 f)
```python
class GameObject:
# ...
def on_collision(self, other: "GameObject"):
pass
class Projectile(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
if not isinstance(other, Ship):
self.alive = False
```
---
# Musterlösung - Aufgabe 8 f)
```python
class StaticObject(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
self.alive = False
class Ship(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
match other:
case Asteroid():
self.hp -= other.radius
case Health():
self.hp += other.amount
case Ammunition():
self.shots += other.amount
```
---
# Musterlösung - Aufgabe 8 f)
```python
@dataclass
class Asteroid(StaticObject):
special: bool
def on_collision(self, other: 'GameObject'):
if not isinstance(other, Asteroid):
self.alive = False
```
---
# Musterlösung - Aufgabe 8 g)
```python
@dataclass
class Ship(GameObject):
# ...
def shoot(self) -> Projectile:
alive = False
if self.shots:
self.shots -= 1
alive = True
pos = Vec2(self.position.x, self.position.y)
return Projectile(pos, 5, alive, (255, 0, 0), 3)
```
---
# Musterlösung - Aufgabe 8 h)
```python
@dataclass
class GameObject:
# ...
def draw(self, screen: pygame.Surface):
pygame.draw.circle(screen, self.color, (self.position.x, self.position.y), self.radius)
```
---
# Override-Dekorator
- ist in `typing`
- Wird über Methoden geschrieben, die überschrieben werden
- Pylance zeigt einen Fehler an, wenn die überschriebene Methode in keiner Oberklasse gefunden wird
- Hilft Fehler vorzubeugen - falsche Signatur, Parameter, ...
---
# Override-Dekorator - Beispiel
```python
from typing import override
from dataclasses import dataclass
@dataclass
class GameObject:
def on_collision(self, other: 'GameObject'):
pass
class StaticObject(GameObject):
@override
def on_collisoin(self, other: 'GameObject'): # Pylance-Error
self.alive = False
```
---
# Override-Dekorator - Beispiel
```python
from typing import override
from dataclasses import dataclass
@dataclass
class GameObject:
def on_collision(self, other: 'GameObject'):
pass
class StaticObject(GameObject):
@override
def on_collision(self): # Pylance-Error
self.alive = False
```
---
# Datenkapselung
- Man möchte manche Implementierung verstecken
- Wenn andere deinen Code verwenden, dann möchte man eine Schnittstelle anbieten die intuitiv ist.
```python
@dataclass
class MyList[T]:
internal_list: list[T] = []
def add(self, item: T) -> None:
self.internal_list += [other]
```
---
# Datenkapselung - warum ist das schlecht?
```python
from my_collections import MyList
xs = MyList()
xs.internal_list # ????
```
- was sollen wir mit `internal_list`?
- andere sollten nur auf `add()` zugreifen können
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
def add(self, item: T):
self.__internal_list += [item]
self.__length += 1
@property
def length(self) -> int:
return self.__length
```
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
```
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
def add(self, item: T):
self.__internal_list += [item]
self.__length += 1
@property
def length(self) -> int:
return self.__length
```
---
# Private Attribute - Setter
- Manchmal wollen wir trotzdem private Attribute setzen
- Aber vielleicht nur wenn bestimmte Bedingungen erfüllt sind
```python
class GameObject:
_position: InitVar[tuple[int, int]]
def __post__init__(self, position: tuple[int, int]):
assert (0, 0) <= position
self.__position = position
@property
def position(self) -> tuple[int, int]:
return self.__position
```
---
# Private Attribute - Setter
```python
@dataclass
class GameObject:
_position: InitVar[tuple[int, int]]
def __post_init__(self, position: tuple[int, int]):
assert (0, 0) > position
self.__position = position
@property
def position(self) -> tuple[int, int]:
return self.__position
@position.setter
def position(self, position: tuple[int, int]):
if (0, 0) > position:
return
self.__position = position
```
---
# Comprehensive-Guide to `class`
## `@dataclass`
- Attribute werden im Klassenrumpf definiert
- können mit einem Standardwert definiert werden
- `__init__`, `__post_init__`, `__repr__`, `__eq__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, ... werden automatisch generiert
- In der Vorlesung benutzen wir nur `dataclass`
---
# Comprehensive-Guide to `class`
## `@dataclass`
- Attribute die im Klassenrumpf definiert werden, werden automatisch in die `__init__` generiert, auch wenn es einen Standardwert gibt!
```python
@dataclass
class A:
x: int
y: int = 0
def __init__(self, x: int, y: int = 0): # das macht @dataclass von selber!
self.x = x
self.y = y
```
---
# Comprehensive-Guide to `class`
## `Enum`
- Wenn man eine endliche Aufzählung braucht (endliche Menge)
- macht die Fallunterscheidung einfach weil es endliche Elemente gibt
- Versichert auch dass kein quatsch übergeben wird wie zb bei `str`
- **niemals** mit `@dataclass`, sonst geht alles kaputt
---
# Enum - Beispiel
```python
def eval[T: (int | float)](operator: str, x: T, y: T) -> T:
match operator:
case '+':
return x + y
case '-':
return x - y
case '*':
return x * y
case '/' if y != 0:
return x / y
case _:
return 0
```
---
# Enum - Beispiel
```python
from enum import Enum, auto
class Op(Enum):
ADD = auto()
SUB = auto()
DIV = auto()
MUL = auto()
```
Jetzt passen wir die Methodensignatur an
```python
def eval[T: (int | float)](operator: Op, x: T, y: T) -> T:
```
Jetzt kann nichts beliebiges als `operator` übergeben werden
---
# Blatt 09 - Fragen?
- Abgabe: 18.12. - 09:00
- Testet euren Code!
- Es gibt keine dummen Fragen wenns ums Verständnis geht

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

View File

@ -1,31 +0,0 @@
from dataclasses import dataclass
@dataclass
class A:
x: int
y: int = 0
# die init die von `dataclass` generiert wird, also unnötig
# siehe class B
def __init__(self, x: int, y: int = 0):
self.x = x
self.y = y
@dataclass
class B:
x: int
y: int = 0
if __name__ == '__main__':
my_a = A(1)
assert my_a.x == 1
assert my_a.y == 0
my_other_a = A(1, y = 1)
assert my_other_a.x == 1
assert my_other_a.y == 1
my_b = B(1)
assert my_b.x == 1
assert my_b.y == 0
my_other_b = B(1, y = 1)
assert my_other_b.x == 1
assert my_other_b.y == 1

View File

@ -1,56 +0,0 @@
from dataclasses import dataclass, InitVar
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self) -> None:
self.__internal_list: list[T] = []
self.__length = 0
def add(self, item: T) -> None:
self.__internal_list += [item]
self.__length += 1
@property
def length(self) -> int:
return self.__length
@dataclass
class GameObject:
_position: InitVar[tuple[int, int]]
def __post_init__(self, position: tuple[int, int]) -> None:
assert (0, 0) <= position
self.__position = position
@property
def position(self) -> tuple[int, int]:
return self.__position
@position.setter
def position(self, position: tuple[int, int]) -> None:
if (0, 0) > position:
return
self.__position = position
if __name__ == "__main__":
xs: MyList[int] = MyList()
xs.add(100)
assert xs.length == 1
position: tuple[int, int] = (0, 0)
my_obj = GameObject(position)
assert my_obj.position == (0, 0)
try:
GameObject((0, -1))
except AssertionError:
pass
else:
raise AssertionError(
f"{my_obj} should have thrown a assertation error")
my_obj.position = (-1, 0)
assert my_obj.position == (0, 0)

View File

@ -1,17 +0,0 @@
---
marp: true
paginate: true
class: invert
# theme: uncover
footer: Tutorium 10 - 22.12.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-10
header:
---
# Tutorium 10 - 22.12.2023
Weihnachtsaufgabe :)
---
# Aufgabe - SpaceArena

View File

@ -1,22 +0,0 @@
from result import Err, Ok, Panick, Result
from ui import run_command, Color
from spacearena import Difficulty, SpaceArena
if __name__ == '__main__':
difficulty: Difficulty | None = None
while not difficulty:
difficulty = Difficulty.get_difficulty(
input(f"Choose your difficulty {[e.name.lower() for e in list(Difficulty)]}\n> ").upper())
print(f"Difficulty: {difficulty} selected!")
game = SpaceArena(difficulty)
print("Starting game!")
while game.is_running:
Err("error value hehe").unwrap_or("yay still works")
match run_command(input("> "), game):
case Ok(value):
print(f"{Color.OK}{value}{Color.ENDC}")
case Err(value):
print(f"{Color.BOLD}{Color.FAIL}Error:{Color.ENDC} {Color.WARNING}{value}{Color.ENDC}")

View File

@ -1,92 +0,0 @@
from abc import ABC
from dataclasses import dataclass
from typing import Callable, overload
class Panick(Exception):
def __init__(self, msg_res: 'Err | str') -> None:
match msg_res:
case str(msg):
super().__init__(msg)
case _:
super().__init__(f"thread `__main__` panicked:\ncalled `Result::unwrap()` on an `Err` value: \"{msg_res.value}\"")
@dataclass
class Result[V, E](ABC):
def unwrap(self) -> V:
match self:
case Ok(value):
return value
raise Panick(self) # type: ignore
def expect(self, msg: str) -> V:
match self:
case Ok(value):
return value
raise Panick(msg)
def is_ok(self) -> bool:
return isinstance(self, Ok)
def is_err(self) -> bool:
return isinstance(self, Err)
def unwrap_or(self, value: V) -> V:
match self:
case Ok(val):
return val
return value
def and_[O](self, other: 'Result[O, E]') -> 'Result[O, E]':
match self, other:
case Err(value), _:
return Err(value)
return other
def and_then[O](self, func: Callable[[V], 'Result[O, E]']) -> 'Result[O, E]':
match self:
case Ok(value):
return func(value)
return Err(self.value) # type: ignore
@dataclass
class Ok[V, E](Result[V, E]):
value: V
@dataclass
class Err[V, E](Result[V, E]):
value: E
def __sqrt(num: float) -> Result[float, str]:
if num < 0:
return Err('negative sqrt')
return Ok(num ** 0.5)
def test_result() -> None:
assert Ok(4.0).and_then(__sqrt) == Ok(2.0)
assert Ok(-5.0).and_then(__sqrt) == Err('negative sqrt')
assert Err("haha error").and_then(__sqrt) == Err("haha error")
assert Ok("test").unwrap() == "test"
try:
Err("haha error").unwrap()
except Panick:
pass
else:
assert False, 'didn\'t throw Panick'
assert Err('haha error').unwrap_or("doch nicht") == 'doch nicht'
result = Ok('test result')
match result:
case Ok(res):
assert res == 'test result'
case Err():
pass
result = Err('test result')
match result:
case Ok():
pass
case Err(res):
assert res == 'test result'

View File

@ -1,44 +0,0 @@
from dataclasses import dataclass, InitVar
from enum import Enum
from typing import Optional
class Difficulty(Enum):
EASY = 0.5
NORMAL = 1.0
HARD = 2.0
@staticmethod
def get_difficulty(input: str) -> Optional['Difficulty']:
for e in list(Difficulty):
if e.name == input:
return e
def __str__(self):
return self.name
@dataclass
class SpaceArena:
_difficulty: InitVar[Difficulty] = Difficulty.NORMAL
def __post_init__(self, difficulty: Difficulty = Difficulty.NORMAL) -> None:
self.__difficulty = difficulty
self.__is_running = True
@property
def difficulty(self) -> Difficulty:
return self.__difficulty
@difficulty.setter
def difficulty(self, new_diff: Difficulty):
self.__difficulty = new_diff
@property
def is_running(self) -> bool:
return self.__is_running
def stop(self):
self.__is_running = False

View File

@ -1,79 +0,0 @@
from abc import ABC, abstractmethod
from enum import Enum
import re
from typing import override
from result import Ok, Err, Result
from spacearena import Difficulty, SpaceArena
class Color(Enum):
OK = '\033[92m'
FAIL = '\033[91m'
WARNING = '\033[93m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def __str__(self) -> str:
return self.value
class AbstractCommand(ABC):
def matches(self, inp: str) -> Result[re.Match[str], None]:
if m := re.match(self.pattern(), inp):
return Ok(m)
return Err(None)
@abstractmethod
def pattern(self) -> str:
pass
@abstractmethod
def run(self, game: SpaceArena, args: list[str]) -> Result[str, str]:
pass
class QuitCommand(AbstractCommand):
@override
def pattern(self) -> str:
return r"(quit)"
@override
def run(self, game: SpaceArena, _: list[str]) -> Result[str, str]:
game.stop()
return Ok('quitting game!')
class DifficultyCommand(AbstractCommand):
@override
def pattern(self) -> str:
return r"(difficulty|diff)\s+(increase|decrease)"
@override
def run(self, game: SpaceArena, args: list[str]) -> Result[str, str]:
diffs = list(Difficulty)
curr = diffs.index(game.difficulty)
match args[0]:
case 'increase' if curr + 1 < len(diffs):
game.difficulty = diffs[curr + 1]
return Ok(f"Increasing difficulty to {game.difficulty}")
case 'increase':
return Err("maximum difficulty")
case 'decrease' if curr - 1 >= 0:
game.difficulty = diffs[curr - 1]
return Ok(f"Decreasing difficulty to {game.difficulty}")
case 'decrease':
return Err("minimum difficulty")
return Err("invalid input")
ALL_COMMANDS: list[AbstractCommand] = [QuitCommand(), DifficultyCommand()]
def run_command(inp: str, game: SpaceArena, commands: list[AbstractCommand] = ALL_COMMANDS) -> Result[str, str]:
for command in commands:
match command.matches(inp):
case Ok(value):
args = [str(s) for s in value.groups()]
return command.run(game, args[1:])
return Err("command not found")

View File

@ -1,381 +0,0 @@
---
marp: true
paginate: true
class: invert
# theme: uncover
footer: Tutorium 11 - 12.01.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-11
header:
---
# Tutorium 11
Dictionary, List-Comprehensions, OOP nochmal
---
## Dictionary
- Eine Ansammlung aus **Keys** und dessen **Werten**
- Ordnet jedem **Key** einen **Wert** zu
- Ein **Key** muss **immutable** sein, also keine `list`, `Objects`, ...
- **Werte** können mutable sein, also eigentlich alles.
---
## Creating a Dictionary
```python
dictionary = {
<key>: <value>,
<key>: <value>,
...
<key>: <value>
}
```
---
## Creating a Dictionary - Beispiel
- `Key`: Modul als `str` referenziert
- **immutable**
- `Value`: Liste aller Stundenten, mutable
- **mutable**, wir können Stunden entfernen/hinzufügen
```python
courses: dict[str, list[str]] = {
"eidp": ["np19", "az34", "jf334"],
"mathe": ["aw331", "pl67"],
"sdp": ["xy111", "xz112"],
}
```
---
## Was passiert wenn wir eine `list` als nehmen?
- `list[Any]` ist mutable, genauer nicht **hashable**
- `hash([1, 2, 3])` wirft einen Error
- `dict` nutzt `hash(...)` für lookups
- `dict[list[Any], Any]` wirft also einen `TypeError`, weil `list` nicht **hashable** ist
- `tuple` sind immutable, wenn dessen Elemente immutable sind
- z.B. `(1, 2)`
---
## Dictionary indizieren
```python
print(courses["eidp"]) # ["np19", "az34", "jf334"]
courses["eidp"] += ["jk331"]
print(courses["eidp"]) # ["np19", "az34", "jf334"]
courses["mathe_2"] += ["jk331"] # KeyError
courses["mathe_2"] = ["jk331"] # fügt "mathe_2" hinzu mit dem Wert ["jk331"]
if "logik" not in courses:
print("logik is not in courses!")
courses["logik"] = []
print("now it is!")
```
---
## Was kann man als Value verwenden?
---
## Was kann man als Value verwenden?
**Alles**!
```python
ops: dict[str, Callable] = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
}
print(ops['+'](3, 1)) # 4
```
---
## Dictionary iterieren
- mit der `items()` Methode bekommt man jeden `Key` mit `Value`
```python
for courses, students in courses.items():
print(f"{courses}: {students}")
```
---
## List-Comprehension
- hattet ihr noch nicht in der Vorlesung
- Viel zu Viele nutzen es schon, und ich will keine 0 Punkte geben
- Syntax-Sugar für das erstellen von Listen basierend auf anderen Listen
```python
even_numbers = [number for number in range(101) if number % 2 == 0] # [0, 2, ..., 100]
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
all_permutations = [(a, b) for a in range(3)
for b in range(3)]
# quiet fast actually (for python)
pythagorean_triples = [(a, b, c) for a in range(101)
for b in range(101)
for c in range(101)
if a ** 2 + b ** 2 == c ** 2]
```
---
## List-Comprehension
```python
# ew no syntax sugar
all_students: set[str] = set()
for _, students in courses.items():
for student in students:
all_students.add(student)
# syntax sugar!
print({student for students in courses.values()
for student in students})
# flattening stuff
matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
print([num for row in matrix for num in row]) # [1, 0, 0, 0, 1, 0, 0, 0, 1]
```
---
## Übertreibt es aber nicht!
- List-Comprehensions sind zum *erstellen* von Listen.
- List-Comprehensions sollten **nichts** *machen*
Also **kein** side effects oder Funktionsaufrufe!
```python
x = [1, 2, 3, 4]
[x.append(num) for num in range(5, 11)] # really bad
```
```python
def f(x: float) -> float:
return x ** 2 + 3 * x + 1
[f(x) for x in range(101)] # bad
```
---
## OOP - Funktion oder Methode?
```python
import math
from dataclasses import dataclass
@dataclass
class Position:
x: float
y: float
def distance(self, other: Position) -> float:
return math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
def distance_of(a: Position, b: Position) -> float:
return math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)
```
---
## OOP - Funktion oder Methode!
- `distance(self, other: Position)` ist eine Methode.
- Gehört zu einer Klasse und hat `self` als Parameter
- `distance_of(a: Position, b: Position)` ist eine Funktion.
- unabhängig (normalerweise kein **state**)
---
## Was ist ein **State** (Zustand)?
```python
class GameState(Enum):
RUNNING = auto()
PAUSED = auto()
ENDED = auto()
@dataclass
class Game:
state: GameState
```
- Unser `Game`-Objekt hat einen Zustand der sich ändern kann
- Unser `Game` kann pausiert, beendet oder am Laufen sein
- Dieser Zustand kann sich ändern
---
## Was ist ein **State** (Zustand)?
```python
@dataclass
class Position:
x: float
y: float
```
Ebenso sind `x` und `y` Zustände von `Position`, wenn auch nicht ganz so offensichtlich.
- Beschreiben das Objekt
- Können sich ändern
---
## Was ist ein **State** (Zustand)?
Wie sieht es mit `distance_of(...)` aus?
```python
def distance_of(a: Position, b: Position) -> float:
return math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)
```
- Verhält sich immer gleich
- also hat keinen **State**
- ändert keine **States**
- manchmal passiert das leider, ist aber ein schlechter Stil!
```python
def move_to(pos: Position, x: float, y: float):
pos.x = x
pos.y = y
```
- Guter Stil ist es eigentlich immer die Parameter in Ruhe zu lassen!
---
## Funktionen mit **State**
- Ihr kriegt 0 Punkte für die gesamte Abgabe.
```python
can_execute = True
def function(x: int) -> int:
global can_execute
if can_execute:
can_execute = False
return x + 1
return x
def can_execute_again():
global can_execute
can_execute = True
```
- Ich meine das ernst mit den 0 Punkten
---
## Dazu zählt auch sowas!
```python
def main():
session = Session()
def do_something():
session.do()
# ...
```
---
## Private, Getter, Setter
Bei `@dataclass`:
- `InitVar` verwenden wenn im Klassenrumpf deklariert
- Sollen mit `_<variable_name>` benannt werden
- `__post_init__(self, <variable>)` muss definiert werden und `__<variable_name>` erstellen!
```python
@dataclass
class Point:
_x: InitVar[float]
_y: InitVar[float]
def __post_init__(self, x, y):
self.__x = x
self.__y = y
```
---
## Private, Getter, Setter
- Geht auch ohne `InitVar`
- Keine Parameter für `__post_init__`
- Also auch keine Parameter beim erstellen
```python
@dataclass
class Point:
def __post_init__(self):
self.__x = 0
self.__y = 0
```
---
## Private, Getter, Setter
- `x` und `y` sind nicht mehr von außen sichtbar
```python
p = Point()
print(p.__x) # Error
```
- außer man erstellt einen *Getter* (`@property`)
```python
@dataclass
class Point:
# ...
@property
def x(self) -> float:
return self.__x
print(Point(3, 1).x) # Prints 3
```
---
## Private, Getter, Setter
Genauso kann man auch private Attribute setzbar machen:
```python
@dataclass
class Point:
# ...
@x.setter
def x(self, new_value: float):
self.__x = new_value
p = Point(3, 1)
print(p.x) # 3
p.x = 4
print(p.x) # 4
```
---
# Fragen?

Binary file not shown.

View File

@ -1,51 +0,0 @@
from dataclasses import InitVar, dataclass
from typing import Any, Callable
def iterating_dict():
courses: dict[str, list[str]] = {
"eidp": ["np19", "az34", "jf334"],
"mathe": ["aw331", "pl67"],
"sdp": ["xy111", "xz112"],
}
for course, students in courses.items():
print(f"{courses}: {students}")
def mutable_keys():
lists_as_keys: dict[list[str], str] = {
["np19", "az34", "jf334"]: "eidp", # type: ignore
["aw331", "pl67"]: "mathe", # type: ignore
["xy111", "xz112"]: "sdp", # type: ignore
}
print(lists_as_keys)
def what_is_a_value():
ops: dict[str, Callable] = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
}
can_execute = True
def function(x: int) -> int:
global can_execute
if can_execute:
can_execute = False
return x + 1
return x
def can_execute_again():
global can_execute
can_execute = True
@dataclass
class Point:
def __post_init__(self):
self.__x = 0
self.__y = 0
@property
def x(self) -> float:
return self.__x

View File

@ -1,424 +0,0 @@
---
marp: true
paginate: true
# class: invert
theme: rose-pine
footer: Tutorium 13 - 26.01.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-13
header:
math: mathjax
---
# Tutorium 13 - 26.01.2024
Orga - Wiederholung Types - Functions! - Decorator
---
# Orga
---
## Wegen fehlendem Tutorium am 19.01.
- Jeder kriegt die 6 Punkte für Anwesenheit
- Auf Blatt 13 als Extrapunkte unter *Anmerkungen*
- Sorry fürs nicht beantworten von manchen Nachrichten
- Falls ihr glaubt ihr bekommt knapp nicht genug Punkte schreibt mich einfach an, man wird schon noch irgendwo Punkte finden
---
## Syntax-Fehler
- Für **Syntax-Fehler** habe ich im allgemeinen **0 Punkte** in der jeweiligen Datei vergeben
- Das euer Programm ausführbar ist sollte das mindeste sein!
- Ihr sollt euer Programm sowieso selbständig testen und ich geh mal davon aus das ist nicht passiert wenn sich die Datei nichtmal ausführen lässt
- Zeitdruck kann ich voll nachvollziehen
---
## Nachträgliches ausbessern
- Ihr verbessert euren SyntaxFehler (eure Python-Datei ist ausführbar)
- Ihr schickt mir eine `.zip` oder eine `.tar.gz` mit dem verbesserten Code an [nils@narl.io](mailto:nils@narl.io)
- verbessert nichts anderes!
- Schreibt kurz in die Mail welches Blatt + Aufgabe + Kürzel
- Ich korrigiere eure Abgabe nachträglich und ihr bekommt zumindest mehr als 0 Punkte
- Bitte nur wenn ihr wirklich die Punkte braucht und habt etwas Geduld mit der Korrektur
---
## Allgemeines
- biete euch Übungen passend zur Klausur
- kein genaues Datum, aber vor dem 09.02
- Klausur ist *wahrscheinlich* am 19.02.
- Short-Link zu der Übung [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
- aktuell noch nicht online
---
# Link: [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
---
# Type annotations
(Wiederholung)
---
## Type annotations - Was ist das?
---
## Type annotations - Was ist das?
* Jedes **Objekt** lässt sich mindestens einem **Typ** zuordnen
* Objekte im mathematischen Sinne wie z.B. Variablen, Funktionen, ...
* Dieser **schränkt** den Wertebereich ein
* z.B. ist eine Variable `x` von Typ `int` eine Ganzzahl
* ähnlich zur mathematischen Schreibweise $x \in \mathbb{Z}$
* In der Informatik nennt man das **Typisierung**
* Es gibt verschiedene Arten der Typisierung
---
## Type annotations - Typisierung
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
- also erst wenn das Programm *läuft*
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
- also während wir den Quellcode übersetzen
---
## Type annotations - Typisierung
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
- also erst wenn das Programm *läuft*
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
- also während wir den Quellcode übersetzen
### Was ist nun Python?
---
### Was ist nun Python?
- **dynamisch typisiert**
- wir müssen unsere `.py` Datei ausführen bevor wir wissen ob alles korrekt ist
- **Pylance** ist ein eigenes Programm
- es soll beim Schreiben bereits **Typverletzungen** erkennen
- **unvollständige** Typüberprüfung, es soll nur den Entwickler unterstützen
---
## Variabeln Typannotieren
* `variable_name: <Type> = ...`
* Beispiele:
```python
x: int = 3
y: int = 5
string: str = "Hello World!"
# aber auch eigene Objekte (OOP)
point: Point = Point(3, 1)
```
* diese Annotation ist für uns **optional**
---
## Funktionen Typannotieren
* `def func_name(param1: <Type>, param2: <Type>, ...) -> <Type>`
* Beispiele:
```python
def add(x: int, y: int) -> int:
return x + y
def div(x: float, y: float) -> Optional[float]:
if y == 0.0:
return None
return x / y
```
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
---
## Klassen Typannotieren
*
```
class ClassName:
attribute_name1: <Type>
attribute_name2: <Type>
...
```
* Beispiel:
```python
@dataclass
class Point:
x: int
y: int
```
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
---
## Methoden Typannotieren
* `def method_name(self, param1: <Type>, ...) -> <Type>`
* Beispiel:
```python
class Point:
x: int
y: int
def distance_from(self, other: 'Point') -> float:
return math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
```
* `self` muss **nicht** Typannotiert werden, kann aber
* `other` hingegen schon, wegen Python muss in der Klasse mit `'` annotiert werden
* diese Annotation ist **verpflichtend**
---
## Datentypen von Datentypen
* Manche Datentypen bauen sich aus anderen Datentypen auf
* z.B. `list` ist eine Liste von Elementen mit einem Typ
* hierfür verwenden wir `[]` um den Datentyp in `list` zu annotieren
```python
def sum(xs: list[int]) -> int:
total: int = 0
for x in xs:
total += x
return total
```
* hierbei ist es wichtig so genau wie möglich zu annotieren!
* diese Annotation ist **verpflichtend**
---
## Häufige Fehler mit verschachtelten Typen
---
## Fehlerquelle - `tuple[...]`
* Tuple haben eine feste größe
* Tuple sind endlich
* Tuple können Elemente mit unterschiedlichen Typen haben
* Die Datentypen der Elemente werden mit einem `,` in `[]` getrennt
* Beispiel:
```python
tup: tuple[int, int, float, str] = (1, 2, 3.0, "hello world")
```
* Diese Annotation ist **verpflichtend**
---
## Fehlerquelle - `dict[...]`
* Dictionary haben genau zwei zu definierende Typen
* **Key**
* **Value**
* Beispiel:
```python
number_dictionary: dict[int, str] = {
0: "zero",
1: "one",
2: "two",
}
```
* Diese Annotation ist **verpflichtend**
* Diese kann weiter geschachtelt werden durch z.B. `list` als `Value`:
* `dict[int, list[str]]`
---
## Fehlerquelle - Typvariabeln (generische Typen)
* manchmal wollen wir nicht genau wissen welchen Datentypen wir haben
* dieser wird dann implizit von Python erkannt
* wir stellen damit sicher dass eine Typvariable **beliebig** aber **fest** ist
* Beispiel:
```python
def add[T](x: T, y: T) -> T:
return x + y
```
* `T` kann nur ein Datentyp sein, also muss `type(x) == type(y)` gelten
* **außer** wir schrenken `T` mit `|` ein: `T: (int | str)` damit müssen x und y nicht den gleichen Datentypen haben
* `T` lässt sich weiter einschränken durch `T: (int, str)`, hierbei ist `T` entweder ein `int` oder (exklusiv) `str`
---
## Fehlerquelle - Was ist TypeVar?
* `TypeVar` ist aus früheren Python-Versionen
* Typvariablen wurden vor der Python 3.12 so definiert:
```python
T = TypeVar('T')
```
* sieht dumm aus, ist es auch, benutzt es nicht!
---
## Fragen zu Typannotationen?
---
# Funktionale Programmierung
---
## Funktionale Programmierung - was ist das?
- Funktionen sind äquivalent zu Datenobjekten
- anonyme Funktionen aka Lambdas
- Closures
- Programmablauf mit Verkettung und Komposition von Funktionen
---
## Funktionen sind Datenobjekte
- Jede Funktion hat den Datentyp `Callable`
- Wir können Funktionen wie alle anderen Objekte variabeln zuweisen
```python
def add(a: int, b: int) -> int:
return a + b
add_but_variable = add
print(add_but_variable(3, 2)) # 5
```
---
## Anonyme Funktionen - `lambda`
- Mit dem `lambda` Keyword lassen sich anonyme Funktionen definieren ohne `def`
- Bietet sich vor allem an für kleine Funktionen und Kompositionen von Funktionen
```python
print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # 10
```
- hat als Datentyp auch `Callable`
```python
add: Callable[[int, int], int] = lambda x, y: x + y
```
---
## Closures
- Verkettete Funktionen, bei denen die Variabeln aus vorherigen benutzt werden können
```python
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
return lambda a, b: lambda c: a * x ** 2 + b * x + c
print(poly(3)(2, 3)(5)) # 2 * 3 ** 2 + 3 * 3 + 5 = 32
```
- kein wirklich schönes Beispiel, ein besseres ist `compose` für Kompositionen
---
## Komposition
- Verketten von Funktionen
```python
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
return fold(lambda f, g: lambda n: f(g(n)), funcs)
f: Callable[[int], int] = lambda n: n + 42
g: Callable[[int], int] = lambda n: n ** 2
h: Callable[[int], int] = lambda n: n - 3
print(compose(f, g, h)(0))
```
---
## Higher-Order Functions
- nehmen eine oder mehrere `Callable` als Argument
- geben ein `Callable` zurück
### Higher-Order-Function - `map`
- Wendet ein `Callable` auf jedes Element in einem `Iterable` an
```python
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
return [func(x) for x in xs]
numeric_list = list(map(lambda e: int(e), ['1', '2', '3']))
print(numeric_list) # [1, 2, 3]
```
---
### Higher-Order-Function - `filter`
- `filter` verarbeitet Datenstrukturen anhand eines Prädikats (`Callable`)
- behält nur Elemente die das Prädikat erfüllen
```python
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
return [x for x in xs if predicate(x)]
predicate: Callable[[int | None] bool] = lambda e: e is not None
none_free_list: list[int] = list(filter(predicate, [1, 2, 3, None, 5, 6]))
print(none_free_list) # [1, 2, 3, 5, 6] - kein None
```
---
### Higher-Order-Function - `fold`
- Kombiniert Elemente einer Datenstruktur
```python
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
it: Iterator[T] = iter(xs)
value: T | None = None
for x in it:
match value:
case None:
value = x
case _:
value = func(value, x)
if not value:
raise TypeError("can't fold empty list")
return value
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
print(sum([1, 2, 3, 4])) # 10
```
---
### keine Higher-Order-Function - `flatten`
- Nimmt mehrdimensionale Listen und macht eine Liste draus
```python
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
new_list = []
for s in xs:
if isinstance(s, Iterable):
new_list += flatten(s)
else:
new_list.append(s)
return new_list
flattened = list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
```
- nimmt weder `Callable` als Argumente
- gibt kein `Callable` zurück
- ist keine Higher-Order-Function
---
# Fragen zur funktionalen Programmierung?
---
# Weitere allgemeine Fragen?

View File

@ -1,21 +0,0 @@
# Notes for Tutorium 12 - 19.01.2024
## Topics
- Type Annotation - The Full Guide
- Basics
- parameter
- variable
- return type
-
- Advanced
- Collections
- ~~args, kwargs~~
- Functional programming
- `lambda`
- map
- filter
- reduce
- flatten
- function composition
- Maybe-Type

Binary file not shown.

View File

@ -1,84 +0,0 @@
from typing import Any, Callable, Iterable, Iterator
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
return [func(x) for x in xs]
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
return [x for x in xs if predicate(x)]
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
it: Iterator[T] = iter(xs)
value: T | None = None
for x in it:
match value:
case None:
value = x
case _:
value = func(value, x)
if not value:
raise TypeError("can't fold empty list")
return value
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
new_list = []
for s in xs:
if isinstance(s, Iterable):
new_list += flatten(s)
else:
new_list.append(s)
return new_list
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
return fold(lambda f, g: lambda n: f(g(n)), funcs)
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
return lambda a, b: lambda c: a * x ** 2 + b * x + c
def main():
f: Callable[[int], int] = lambda n: n + 42
g: Callable[[int], int] = lambda n: n ** 2
h: Callable[[int], int] = lambda n: n - 3
fhg: Callable[[int], int] = compose(f, g, h)
# f(g(h(0))) <=> ((0 - 3) ** 2) + 42 = 51
assert (tmp := fhg(0)) == 51
assert compose(f, g, h)(0) == 51
predicate = lambda e: e
assert list(filter(predicate, [1, 2, 3, None, 5, 6])) == [1, 2, 3, 5, 6]
assert list(filter(lambda e: e is None, [1, 2, 3, None, 5, 6])) == [None]
assert list(map(lambda e: str(e), [1, 2, 3, 4, 5, 6, "hello_functional"])) == ["1", "2", "3", "4", "5", "6", "hello_functional"]
assert list(
filter(lambda e: len(e) > 1,
map(lambda e: str(e),
[1, 2, 3, 4, "hello_world"]))) == ["hello_world"]
assert list(filter(lambda e: isinstance(e, int), [1, 2, 3, "hello"])) == [1, 2, 3]
assert (tmp := list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))) == [1, 2, 3, 4, 5, 6, 7, 8, 9], f"{tmp}"
def add(a: int, b: int) -> int:
return a + b
add_but_variable: Callable[[int, int], int] = add
assert add_but_variable(3, 2) == 5
add2: Callable[[int, int], int] = lambda x, y: x + y
assert add2(2, 3) == 5
assert (lambda x, y: x + y)(3, 4) == 7
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
assert sum([1, 2, 3, 4]) == 10
assert poly(3)(2, 3)(5) == 2 * 3 ** 2 + 3 * 3 + 5
if __name__ == '__main__':
main()

View File

@ -1,424 +0,0 @@
---
marp: true
paginate: true
# class: invert
theme: rose-pine
footer: Tutorium 13 - 26.01.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-13
header:
math: mathjax
---
# Tutorium 13 - 26.01.2024
Orga - Wiederholung Types - Functions!
---
# Orga
---
## Wegen fehlendem Tutorium am 19.01.
- Jeder kriegt die 6 Punkte für Anwesenheit
- Auf Blatt 13 als Extrapunkte unter *Anmerkungen*
- Sorry fürs nicht beantworten von manchen Nachrichten
- Falls ihr glaubt ihr bekommt knapp nicht genug Punkte schreibt mich einfach an, man wird schon noch irgendwo Punkte finden
---
## Syntax-Fehler
- Für **Syntax-Fehler** habe ich im allgemeinen **0 Punkte** in der jeweiligen Datei vergeben
- Das euer Programm ausführbar ist sollte das mindeste sein!
- Ihr sollt euer Programm sowieso selbständig testen und ich geh mal davon aus das ist nicht passiert wenn sich die Datei nichtmal ausführen lässt
- Zeitdruck kann ich voll nachvollziehen
---
## Nachträgliches ausbessern
- Ihr verbessert euren SyntaxFehler (eure Python-Datei ist ausführbar)
- Ihr schickt mir eine `.zip` oder eine `.tar.gz` mit dem verbesserten Code an [nils@narl.io](mailto:nils@narl.io)
- verbessert nichts anderes!
- Schreibt kurz in die Mail welches Blatt + Aufgabe + Kürzel
- Ich korrigiere eure Abgabe nachträglich und ihr bekommt zumindest mehr als 0 Punkte
- Bitte nur wenn ihr wirklich die Punkte braucht und habt etwas Geduld mit der Korrektur
---
## Allgemeines
- biete euch Übungen passend zur Klausur
- kein genaues Datum, aber vor dem 09.02
- Klausur ist *wahrscheinlich* am 19.02.
- Short-Link zu der Übung [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
- aktuell noch nicht online
---
# Link: [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
---
# Type annotations
(Wiederholung)
---
## Type annotations - Was ist das?
---
## Type annotations - Was ist das?
* Jedes **Objekt** lässt sich mindestens einem **Typ** zuordnen
* Objekte im mathematischen Sinne wie z.B. Variablen, Funktionen, ...
* Dieser **schränkt** den Wertebereich ein
* z.B. ist eine Variable `x` von Typ `int` eine Ganzzahl
* ähnlich zur mathematischen Schreibweise $x \in \mathbb{Z}$
* In der Informatik nennt man das **Typisierung**
* Es gibt verschiedene Arten der Typisierung
---
## Type annotations - Typisierung
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
- also erst wenn das Programm *läuft*
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
- also während wir den Quellcode übersetzen
---
## Type annotations - Typisierung
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
- also erst wenn das Programm *läuft*
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
- also während wir den Quellcode übersetzen
### Was ist nun Python?
---
### Was ist nun Python?
- **dynamisch typisiert**
- wir müssen unsere `.py` Datei ausführen bevor wir wissen ob alles korrekt ist
- **Pylance** ist ein eigenes Programm
- es soll beim Schreiben bereits **Typverletzungen** erkennen
- **unvollständige** Typüberprüfung, es soll nur den Entwickler unterstützen
---
## Variabeln Typannotieren
* `variable_name: <Type> = ...`
* Beispiele:
```python
x: int = 3
y: int = 5
string: str = "Hello World!"
# aber auch eigene Objekte (OOP)
point: Point = Point(3, 1)
```
* diese Annotation ist für uns **optional**
---
## Funktionen Typannotieren
* `def func_name(param1: <Type>, param2: <Type>, ...) -> <Type>`
* Beispiele:
```python
def add(x: int, y: int) -> int:
return x + y
def div(x: float, y: float) -> Optional[float]:
if y == 0.0:
return None
return x / y
```
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
---
## Klassen Typannotieren
*
```
class ClassName:
attribute_name1: <Type>
attribute_name2: <Type>
...
```
* Beispiel:
```python
@dataclass
class Point:
x: int
y: int
```
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
---
## Methoden Typannotieren
* `def method_name(self, param1: <Type>, ...) -> <Type>`
* Beispiel:
```python
class Point:
x: int
y: int
def distance_from(self, other: 'Point') -> float:
return math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
```
* `self` muss **nicht** Typannotiert werden, kann aber
* `other` hingegen schon, wegen Python muss in der Klasse mit `'` annotiert werden
* diese Annotation ist **verpflichtend**
---
## Datentypen von Datentypen
* Manche Datentypen bauen sich aus anderen Datentypen auf
* z.B. `list` ist eine Liste von Elementen mit einem Typ
* hierfür verwenden wir `[]` um den Datentyp in `list` zu annotieren
```python
def sum(xs: list[int]) -> int:
total: int = 0
for x in xs:
total += x
return total
```
* hierbei ist es wichtig so genau wie möglich zu annotieren!
* diese Annotation ist **verpflichtend**
---
## Häufige Fehler mit verschachtelten Typen
---
## Fehlerquelle - `tuple[...]`
* Tuple haben eine feste größe
* Tuple sind endlich
* Tuple können Elemente mit unterschiedlichen Typen haben
* Die Datentypen der Elemente werden mit einem `,` in `[]` getrennt
* Beispiel:
```python
tup: tuple[int, int, float, str] = (1, 2, 3.0, "hello world")
```
* Diese Annotation ist **verpflichtend**
---
## Fehlerquelle - `dict[...]`
* Dictionary haben genau zwei zu definierende Typen
* **Key**
* **Value**
* Beispiel:
```python
number_dictionary: dict[int, str] = {
0: "zero",
1: "one",
2: "two",
}
```
* Diese Annotation ist **verpflichtend**
* Diese kann weiter geschachtelt werden durch z.B. `list` als `Value`:
* `dict[int, list[str]]`
---
## Fehlerquelle - Typvariabeln (generische Typen)
* manchmal wollen wir nicht genau wissen welchen Datentypen wir haben
* dieser wird dann implizit von Python erkannt
* wir stellen damit sicher dass eine Typvariable **beliebig** aber **fest** ist
* Beispiel:
```python
def add[T](x: T, y: T) -> T:
return x + y
```
* `T` kann nur ein Datentyp sein, also muss `type(x) == type(y)` gelten
* **außer** wir schrenken `T` mit `|` ein: `T: (int | str)` damit müssen x und y nicht den gleichen Datentypen haben
* `T` lässt sich weiter einschränken durch `T: (int, str)`, hierbei ist `T` entweder ein `int` oder (exklusiv) `str`
---
## Fehlerquelle - Was ist TypeVar?
* `TypeVar` ist aus früheren Python-Versionen
* Typvariablen wurden vor der Python 3.12 so definiert:
```python
T = TypeVar('T')
```
* sieht dumm aus, ist es auch, benutzt es nicht!
---
## Fragen zu Typannotationen?
---
# Funktionale Programmierung
---
## Funktionale Programmierung - was ist das?
- Funktionen sind äquivalent zu Datenobjekten
- anonyme Funktionen aka Lambdas
- Closures
- Programmablauf mit Verkettung und Komposition von Funktionen
---
## Funktionen sind Datenobjekte
- Jede Funktion hat den Datentyp `Callable`
- Wir können Funktionen wie alle anderen Objekte variabeln zuweisen
```python
def add(a: int, b: int) -> int:
return a + b
add_but_variable = add
print(add_but_variable(3, 2)) # 5
```
---
## Anonyme Funktionen - `lambda`
- Mit dem `lambda` Keyword lassen sich anonyme Funktionen definieren ohne `def`
- Bietet sich vor allem an für kleine Funktionen und Kompositionen von Funktionen
```python
print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # 10
```
- hat als Datentyp auch `Callable`
```python
add: Callable[[int, int], int] = lambda x, y: x + y
```
---
## Closures
- Verkettete Funktionen, bei denen die Variabeln aus vorherigen benutzt werden können
```python
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
return lambda a, b: lambda c: a * x ** 2 + b * x + c
print(poly(3)(2, 3)(5)) # 2 * 3 ** 2 + 3 * 3 + 5 = 32
```
- kein wirklich schönes Beispiel, ein besseres ist `compose` für Kompositionen
---
## Komposition
- Verketten von Funktionen
```python
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
return fold(lambda f, g: lambda n: f(g(n)), funcs)
f: Callable[[int], int] = lambda n: n + 42
g: Callable[[int], int] = lambda n: n ** 2
h: Callable[[int], int] = lambda n: n - 3
print(compose(f, g, h)(0))
```
---
## Higher-Order Functions
- nehmen eine oder mehrere `Callable` als Argument
- geben ein `Callable` zurück
### Higher-Order-Function - `map`
- Wendet ein `Callable` auf jedes Element in einem `Iterable` an
```python
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
return [func(x) for x in xs]
numeric_list = list(map(lambda e: int(e), ['1', '2', '3']))
print(numeric_list) # [1, 2, 3]
```
---
### Higher-Order-Function - `filter`
- `filter` verarbeitet Datenstrukturen anhand eines Prädikats (`Callable`)
- behält nur Elemente die das Prädikat erfüllen
```python
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
return [x for x in xs if predicate(x)]
predicate: Callable[[int | None] bool] = lambda e: e is not None
none_free_list: list[int] = list(filter(predicate, [1, 2, 3, None, 5, 6]))
print(none_free_list) # [1, 2, 3, 5, 6] - kein None
```
---
### Higher-Order-Function - `fold`
- Kombiniert Elemente einer Datenstruktur
```python
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
it: Iterator[T] = iter(xs)
value: T | None = None
for x in it:
match value:
case None:
value = x
case _:
value = func(value, x)
if not value:
raise TypeError("can't fold empty list")
return value
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
print(sum([1, 2, 3, 4])) # 10
```
---
### keine Higher-Order-Function - `flatten`
- Nimmt mehrdimensionale Listen und macht eine Liste draus
```python
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
new_list = []
for s in xs:
if isinstance(s, Iterable):
new_list += flatten(s)
else:
new_list.append(s)
return new_list
flattened = list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
```
- nimmt weder `Callable` als Argumente
- gibt kein `Callable` zurück
- ist keine Higher-Order-Function
---
# Fragen zur funktionalen Programmierung?
---
# Weitere allgemeine Fragen?

Binary file not shown.

View File

@ -1,153 +0,0 @@
---
marp: true
paginate: true
# class: invert
theme: rose-pine
footer: Tutorium 14 - 02.02.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-14
header:
math: mathjax
---
# Tutorium 14 - 02.02.2024
Decorator, Testing
---
# Decorator
- **Design-Pattern**, oft auch **Wrapper** genannt
- Verpackt ein Objekt um **zusätzliche Funktionalität** zu bieten
- Funktionen sind auch Objekte
- eine Klasse ist ein Objekt
- Oft einfach **syntax sugar**
---
## Beispiel - execute_two_times
```python
def execute_two_times(fn: Callable[..., Any]) -> Callable[..., Any]:
def wrapper(*args, **kwargs)
fn(*args, **kwargs)
fn(*args, **kwargs)
return wrapper
return wrapper
@execute_two_times()
def print_two_times(msg: str):
print(msg)
print_two_times("hello") # hello
# hello
```
---
## Beispiel - execute_by
```python
def execute_by(n: int):
def wrapper(fn):
def wrapped_fn(*args, **kwargs):
for _ in range(0, n):
fn(*args, **kwargs)
return wrapped_fn
return wrapped_fn
return wrapper
@execute_by(10)
def print_ten_times(msg: str):
print(msg)
print_ten_times("hello") # hello
# hello
# ... (10 mal)
```
---
## Beispiel - CommandExecutor
```python
class CommandExecutor[R]:
def __init__(self):
self.__commands: dict[str, Callable[..., R]] = {}
```
---
## Beispiel - run
```python
def run(self, name: str, *args, **kwargs) -> list[R]:
results : list[R] = []
for command_name, command in self.__commands.items():
if command_name == name:
results += [command(*args, **kwargs)]
return results
```
---
## Beispiel - register
```python
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
self.__commands[cmd.__name__] = cmd
return cmd
```
---
## Beispiel - CommandExecutor
```python
class CommandExecutor[R]:
def __init__(self):
self.__commands: dict[str, Callable[..., R]] = {}
def run(self, name: str, *args, **kwargs) -> list[R]:
results : list[R] = []
for command_name, command in self.__commands.items():
if command_name == name:
results += [command(*args, **kwargs)]
return results
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
self.__commands[cmd.__name__] = cmd
return cmd
```
---
## Beispiel - How to use
```python
app = CommandExecutor[str]()
@app.register
def hello_world() -> str:
return 'hello_world'
@app.register
def divide(a: int, b: int) -> str:
if b == 0:
return "tried to divide by zero"
return str(a / b)
print(app.run('hello_world'))
print(app.run('divide', 5, 0))
print(app.run('divide', 10, 2))
```
---
## Decorator in der Klausur
- Waren noch nie Bestandteil der Klausur
- Mut zur Lücke
- Kann euch natürlich nichts versprechen
---
# Testen mit `pytest`

Binary file not shown.

View File

@ -1,36 +0,0 @@
from typing import Callable, Iterator
class CommandExecutor[R]:
def __init__(self):
self.__commands: dict[str, Callable[..., R]] = {}
def run(self, name: str, *args, **kwargs) -> list[R]:
results : list[R] = []
for command_name, command in self.__commands.items():
if command_name == name:
results += [command(*args, **kwargs)]
return results
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
self.__commands[cmd.__name__] = cmd
return cmd
if __name__ == '__main__':
app = CommandExecutor[str]()
@app.register
def hello_world() -> str:
return 'hello_world'
@app.register
def divide(a: int, b: int) -> str:
if b == 0:
return "tried to divide by zero"
return str(a / b)
print(app.run('hello_world'))
print(app.run('divide', 5, 0))
print(app.run('divide', 10, 2))

View File

@ -1,67 +0,0 @@
from typing import Any, Callable
def count_calls[T](func: Callable[..., T]) -> Callable[..., T]:
count_calls.calls = 0
def wrapper(*args, **kwargs) -> T:
init_calls = count_calls.calls
count_calls.calls += 1
result = func(*args, **kwargs)
wrapper.calls = count_calls.calls - init_calls
return result
return wrapper
def f(x: int, y: int) -> int:
if x % 2 == 0:
return x // 2
else:
return x + 2 * y - 1
def count_iterations(a: int, b: int) -> int:
f_a(a, a, b)
return f_a.calls - 1
@count_calls
def f_a(init: int, a: int, b: int) -> None:
if a < b:
return
return f_a(init, f(a, init), b)
def execute_by(n: int):
def wrapper(fn):
def wrapped_fn(*args, **kwargs):
for _ in range(0, n):
fn(*args, **kwargs)
return wrapped_fn
return wrapped_fn
return wrapper
@execute_by(10)
def hello_world():
print('hello world!')
@execute_by(10)
def print_ten_times(msg: str):
print(msg)
def execute_two_times(fn) -> Callable[..., Any]:
def wrapper(*args, **kwargs):
for _ in range(0, 2):
fn(*args, **kwargs)
return wrapper
return wrapper
@execute_two_times
def test(msg: str):
print(msg)
if __name__ == '__main__':
assert (i := count_iterations(7, 6)) == 3, i
assert (i := count_iterations(3, 2)) == 4, i
assert (i := count_iterations(13, 9)) == 18, i
assert (i := count_iterations(13, 10)) == 8, i
assert (i := count_iterations(3, 4)) == 0, i
print_ten_times("hello world")
test("hello")

View File

@ -1,185 +0,0 @@
---
marp: true
paginate: true
# class: invert
theme: rose-pine
footer: Tutorium 15 - 09.02.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-15
header:
math: mathjax
---
# Tutorium 15 - 19.02.2024
Orga, Test-Exam, Regex (Exkurs)
---
# Orga
---
## Orga - Punkte, Vorstellen und einscannen
- Ich habe bei **allen** auf Blatt 12 (oder dem letzten korrigierten) `+6p` für das **verpasste Tutorium** vergeben
- Ich habe für **heute** bereits allen die **Anwesenheitspunkte + Vorstellen** eingetragen
Alle auf die das Zutrifft sind:
`as2037, at359, au56, aw616, bo35, cl393, dk446, eh224, eh295, fk439, fv100, ib180, jb1484, jx20, lf409, ln200, lp269, lp321, ls818, mk1518, mr824, mt367, mw793, mz144, mz242, nm320, no43, pk375, rh295, rl173, rw208, sn205, tr211, ua28, vb202, vb205, vr110, yp39, zj11`
Bei Problemen oder Rückfragen einfach per mail [nils@narl.io](mailto:nils@narl.io) oder nach dem Tutorium
---
## Orga - Klausur
- Klausur am 19.02.
- Es gibt vorraussichtlich zwei Termine
- 2 Stunden
- keine unterschiedlichen Klausuren
- Wo, Wann?
- individuell
- https://courses.laurel.informatik.uni-freiburg.de/courses/2023WS-EiP/exam
- https://s.narl.io/s/termin
- Klausurumgebung ausprobieren unter
- https://bwlehrpool.ruf.uni-freiburg.de/guacamole
- https://s.narl.io/s/examvm
---
## Orga - Vorbereitung auf Klausur
- Macht Altklausuren
- Übungsaufgaben im Git
- https://git.narl.io/nvrl/eidp-klausuraufgaben-2023
- https://s.narl.io/s/eidp-ub
- Wenn ihr die Probeklausur gut hinbekommen habt (**auch Zeitlich!!!**) seid ihr eig safe
- Zusatztutorium mit Dani und mir
---
## Orga - Zusatztutorium von Dani und mir
- Wir machen Altklausuren/Übungsaufgaben
- Zu zweit kann man sich etwas persönlicher kümmern
- Gibt obv. keine Punkte, wir machen das auch nur freiwillig
- Wann, Wo?
- Mittwoch
- x.xx Uhr open end
- Hier in 101
- Es folgt auch noch eine E-Mail an alle über dessen Uni-Mail mit allen Infos
---
# Test-Exam
---
## Test-Exam - Datenklassen
- Ihr könnt **private** Attribute nicht in einer Unterklasser verwenden!
- Mit `super().post_init(...)` könnt ihr diese trotzdem setzen
- `self.__privet_attribute` in einer Unterklasse führt zu einem Fehler
- Es gibt `protected` welches von Außen nicht sichtbar ist, aber in Unterklassen
- `_protected_attribute` welche mit einem `_` annotiert werden
- Beißt sich leider etwas mit `InitVar[...]` von `dataclasses`
- Vergesst am besten `private`, `public` für die Klausur :) versprechen kann ich aber nichts
---
## Test-Exam - Automata
- Bitte kein `T` oder Trash State in der Klausur, außer es ist explizit gefordert
- Ein State bei dem invalide Eingaben hingeschoben werden
- Auch wenn das die Musterlösung von Exercise-13 gemacht hat
- Und auch wenn es eigentlich sinnvoller ist, weil wir wollen nicht bei einer falschen Eingabe dass unser Programm abstürzt
```python
class State(Enum):
q0 = auto()
q1 = auto()
q2 = auto()
```
---
## Test-Exam - Automata
```python
def delta(state: State, input: str) -> State:
match state, input:
case State.q0, "a":
return State.q1
case State.q0, "b":
return State.q2
case State.q1, "a":
return State.q0
case State.q1, "b":
return State.q1
case State.q2, "a":
return State.q2
case State.q2, "b":
return State.q1
case _:
raise ValueError("invalid state or input")
```
---
# ReGex
---
## Was ist ein ReGex?
- Ein regulärer Ausdruck ist ein **match pattern** in einem **text**
- Genau gesagt bildet es eine Menge von Zeichenketten (eine **Sprache**)
- Ihr habt bereits ReGex benutzt
- Wenn ihr z.B. im Browser Ctrl+F drückt und nach einem Wort sucht
- das Wort ist dann ein ReGex
- Es gibt aber auch deutlich komplexere ReGex
---
## Automaten schon wieder
- Was ist wenn wir einen Eingabe-String überprüfen wollen ob er
- mit `a` beginnt
- dann mindest ein, aber beliebig viele `b` folgen
- und mit einem `a` endet
- Wir können einen Akzeptor zeichnen! (nicht-deterministischen endlichen Automaten mit akzeptierenden Zustand)
---
![img not found width:1080](./img/dfa.png)
---
## ReGex - Python
- In Python haben wir `re` also Modul
- Ein ReGex ist eine Zeichenkette
- `"ab"` akzeptiert `"ab"`
- `re.fullmatch(r"ab", "ab")`
- Es gibt Sonderzeichen wie `*, +, (, ), ...` mit denen man komplexere Eingaben überprüfen kann
- Wir wollen `"ab...a"` von der vorherigen Slide matchen
- `b*` möchte 0 bis unendlich `b`
- `b+` möchte 1 bis unendlich `b`
- also `re.fullmatch(r"ab+a", "abbbbbbba")` ist ein Match
---
## Weiter Sonderzeichen/Variabeln
- Mit `\d` kann man in Python eine beliebige Zahl meinen
- Mit `\s` kann man ein beliebigen Whitespace meinen
- So kann man z.B. eine beliebige Ip so darstellen
- `r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'`
- Nützlich zum Parsen oder auch Testen
- Ich nutze z.b. ReGex um eure Aufgaben zu testen
---
# Viel Erfolg bei der Klausur!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

View File

@ -1,51 +0,0 @@
from dataclasses import dataclass
# (a) Vehicle
@dataclass
class Vehicle:
seats: int
hp: int
ccm: int
weight: int
def __post_init__(self):
assert 0 < self.seats < 10
assert 0 < self.hp
assert 0 < self.ccm
assert 0 < self.weight
def fun_factor(self) -> float:
return (10 * self.hp + self.ccm) / self.weight
def __gt__(self, other: 'Vehicle') -> bool:
return self.fun_factor() > other.fun_factor()
# (b) Car
@dataclass
class Car(Vehicle):
spoiler: bool
def fun_factor(self) -> float:
return super().fun_factor() + (0.2 if self.spoiler else 0)
# (c) Motorcycle
@dataclass
class Motorcycle(Vehicle):
sidecar: bool
def __post_init__(self):
super().__post_init__()
if self.sidecar:
assert 2 <= self.seats <= 3
else:
assert 1 <= self.seats <= 2
def fun_factor(self) -> float:
return super().fun_factor() * (2.4 if self.sidecar else 3)

View File

@ -1,84 +0,0 @@
from dataclasses import InitVar, dataclass
# (a) Vehicle
@dataclass
class Vehicle:
_seats: InitVar[int]
_hp: InitVar[int]
_ccm: InitVar[int]
_weight: InitVar[int]
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int):
assert 0 < seats < 10
assert 0 < hp
assert 0 < ccm
assert 0 < weight
self.__seats = seats
self.__hp = hp
self.__ccm = ccm
self.__weight = weight
def fun_factor(self) -> float:
return (10 * self.__hp + self.__ccm) / self.__weight
def __gt__(self, other: 'Vehicle') -> bool:
return self.fun_factor() > other.fun_factor()
@property
def seats(self) -> int:
return self.__seats
@property
def hp(self) -> int:
return self.__hp
@property
def ccm(self) -> int:
return self.__ccm
@property
def weight(self) -> int:
return self.__weight
# (b) Car
@dataclass
class Car(Vehicle):
_spoiler: InitVar[bool]
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int, spoiler: bool):
super().__post_init__(seats, hp, ccm, weight)
self.__spoiler = spoiler
def fun_factor(self) -> float:
return super().fun_factor() + (0.2 if self.__spoiler else 0)
@property
def has_spoiler(self) -> bool:
return self.__spoiler
# (c) Motorcycle
@dataclass
class Motorcycle(Vehicle):
_sidecar: InitVar[bool]
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int, sidecar: bool):
if sidecar:
assert 2 <= seats <= 3
else:
assert 1 <= seats <= 2
super().__post_init__(seats, hp, ccm, weight)
self.__sidecar = sidecar
def fun_factor(self) -> float:
return super().fun_factor() * (2.4 if self.__sidecar else 3)
@property
def is_sidecar(self) -> bool:
return self.__sidecar

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB