Compare commits

..

18 Commits

Author SHA1 Message Date
1efd3b66b0 added graphs 2025-02-06 15:34:01 +01:00
483c6ee562 Update README.md 2024-09-20 17:14:17 +02:00
6f733ce98e Update README.md 2024-09-20 10:10:43 +02:00
c83f33cfca Update recursion/recursive_datastructure/solution/lists.py 2024-02-17 17:02:47 +01:00
c7c1f3ea98 added BST, but remove not working 2024-02-15 04:40:13 +01:00
4c5d27e8d1 added some recursive datastructures 2024-02-14 18:44:59 +01:00
36b6a78833 strings 2024-02-14 01:14:59 +01:00
14cd9fe8b0 added README.md for List-Comprehension exercise 2024-02-11 00:20:19 +01:00
1121c6a171 added prime generator 2024-01-30 17:18:24 +01:00
9ea341183d renamed test.py 2024-01-30 16:31:20 +01:00
801b641fb2 added README.md to list comprehensions 2024-01-30 16:14:14 +01:00
f3e49c96cd added exercises for list comprehensions 2024-01-30 15:50:40 +01:00
f89435117a added :D 2024-01-30 14:34:14 +01:00
56c0d4bec3 added string exercise 2024-01-30 11:21:28 +01:00
407389a5cb added string exercise 2024-01-30 11:15:45 +01:00
da878ec3e6 added sorting exercise 2024-01-30 05:01:05 +01:00
8ed82e01aa fixed formatting 2024-01-30 02:17:48 +01:00
23f4c7be52 fixed formatting 2024-01-30 02:15:09 +01:00
33 changed files with 1384 additions and 18 deletions

View File

@@ -1,33 +1,33 @@
# Übungsaufgaben zur EidP (WS2023) Klausur
# Übungsaufgaben zur EidP (WS2024) Klausur
Alle Aufgaben hier behandeln Konzepte aus der Vorlesung **Einführung in die Programmierung** von der Albert-Ludwig-Universität Freiburg. Hierbei handelt es sich um selbsterstellte Aufgaben der EidP-Tutoren [**Nils Pukropp**](mailto:nils@narl.io) und [**Daniel Mironow**](mailto:mail@danielmironov.dev) die bei der Vorbereitung auf die Klausur helfen sollen.
Alle Aufgaben hier behandeln Konzepte aus der Vorlesung **Einführung in die Programmierung** von der Albert-Ludwig-Universität Freiburg. Hierbei handelt es sich um selbsterstellte Aufgaben der EidP-Tutoren [**Nils Pukropp**](mailto:nils@narl.io) und [**Daniel Mironow**](mailto:mail@danielmironov.dev) die bei der Vorbereitung auf die Klausur helfen sollen. :D
## Reihenfolge der Themen
Es gibt keine direkte Reihenfolge, lediglich Themen die sich teilweise überschneiden. Dennoch gibt es eine Reihenfolge nach Wichtigkeit der Themen:
- [Grundkonzept Schleifen (`for`, `while`, ...)](./loops)
- [Grundkonzept Schleifen (`for`, `while`, ...)](src/branch/main/loops)
- allgemeine Knobelaufgaben rund um Schleifen
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
- [Zeichenketten (Strings `str`)](./strings)
- [Zeichenketten (Strings `str`)](src/branch/main/strings)
- allgemeine Knobelaufgaben rund um `str`
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
- [Dataclasses (OOP `@dataclass`)](./dataclasses)
- [Dataclasses (OOP `@dataclass`)](src/branch/main/dataclasses)
- Objekt orientierte Programmierung mit `@dataclass`
- Einfach (Auswendig lernen)
- [Pattern Matching (`match`)](./pattern_matching)
- [Pattern Matching (`match`)](src/branch/main/pattern_matching)
- Intensive Übungen zu `match`
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
- [Typvariabeln (Generics `[T]`)](./generics)
- [Typvariabeln (Generics `[T]`)](src/branch/main/generics)
- Platzhalter Variabeln um generische Typannotation umzusetzen
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
- [Rekursion (Tree)](./recursion)
- [Rekursion (Tree)](src/branch/main/recursion)
- Sich selbst aufrufende Funktionen
- Schwer, da das Konzept etwas verwirrend ist, aber gut für schnelle Punkte in der Klausur!
- [Generator](./generator)
- [Generator](src/branch/main/generator)
- Erzeugen von Iteratoren auf die seltsame Python Art und Weise!
- Mittel, da das Konzept etwas seltsam ist. Muss man einfach ein paar mal machen!
- [Funktionale Programmierung](./functional_programming)
- [Funktionale Programmierung](src/branch/main/functional_programming)
- Programmieren-Paradigma bei dem der Programmfluss durch Funktionen bestimmt wird!
- Schwer, da das Konzept etwas schwer zu verstehen ist und viele Grundlagen vorraussetzt
@@ -44,11 +44,6 @@ Es gibt keine direkte Reihenfolge, lediglich Themen die sich teilweise überschn
- Zunächst braucht Ihr `pytest` welches ihr mit `pip install -m pytest` installieren könnt
- Könnt auch gerne nachfragen wenn was nicht funktioniert!
- Dann könnt ihr einfach die Tests mit `pytest` in der Konsole aufrufen
- Schlagen die Tests fehl sieht das so aus:
![image not found](https://cloud.narl.io/s/8Dj4E79RKnHZQNJ/preview)
- Hier sagt euch Pytest auch was alles nicht an eurem Code funktioniert
- Funktioniert euer Code sieht das so aus:
![image not found](https://cloud.narl.io/s/2HGdaiQkP4YEQ5K/preview)
## Kontakt
@@ -59,4 +54,4 @@ Es gibt keine direkte Reihenfolge, lediglich Themen die sich teilweise überschn
- Daniel Mironow (Tutor)
- [E-Mail](mailto:mail@danielmironov.dev)
- [Discord](https://discord.com/users/236939658301407243)
- [Discord-Server](https://discord.gg/naeprrX7hB)
- [Discord-Server](https://discord.gg/naeprrX7hB)

View File

@@ -0,0 +1 @@
# primes generator

View File

View File

@@ -0,0 +1,29 @@
from typing import Iterator
def is_prime(n: int) -> bool:
if n < 2:
return False
for d in range(2, n // 2 + 1):
if n % d == 0:
return False
return True
def primes() -> Iterator[int]:
num = 2
while True:
if is_prime(num):
yield num
num += 1
def prime_factorize(n: int) -> Iterator[int]:
it = primes()
num = n
while num > 1:
if num % (prime := next(it)) != 0:
continue
num //= prime
it = primes()
yield prime

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
# Graphen
## Graph-Klasse
Wir wollen hier einen Graphen modellieren, Graphen kennt ihr bereits als Bäume. Diese sind spezielle Graphen welche eine Wurzel haben, also den ersten Knoten. Und dann immer maximal zwei weitere Knoten (**left** und **right**). Die letzten Knoten nennt man auch Blätter. Allgemeine Graphen hingegen kann jeder Knoten auf beliebig viele weitere Knoten verweisen. Diese Verweise nennt man Kanten.
Unsere **Graph** Klasse modellieren wir etwas anders mit einem `dict` welcher immer einen Knoten auf eine Liste von weiteren Knoten mappt.
```python
from dataclasses import dataclass, field
@dataclass
class Graph[T]:
vertecies: dict[T, list[T]] = field(default_factory=dict)
```
Als Beispiel:
![image not found](image.png)
würde dann im Code so aussehen:
```python
my_graph: Graph[str] = Graph(
vertecies={
'A': ['B', 'D'],
'B': ['A', 'C'],
'C': ['B', 'D'],
'D': ['C', 'A'],
})
```

View File

@@ -0,0 +1,60 @@
type Graph[T] = dict[T, list[T]]
def is_bidirected[T](graph: Graph[T]) -> bool:
for a, vertecies in graph.items():
for b in vertecies:
if a not in graph[b]:
return False
return True
def depth_first_search[T](graph: Graph[T], node: T,
_visited: set[T] = None) -> set[T]:
if _visited is None:
_visited = set()
if node in _visited:
return set()
_visited.add(node)
for neighbours in graph[node]:
depth_first_search(graph, neighbours, _visited)
return _visited
def all_edges[T](graph: Graph[T]) -> set[tuple[T, T]]:
all_vertecies = set()
for a, vertecies in graph.items():
for b in vertecies:
all_vertecies.add((a, b))
return all_vertecies
def alt_all_edges[T](graph: Graph[T]) -> set[tuple[T, T]]:
return {(a, b)
for a, vertecies in graph.items()
for b in vertecies}
if __name__ == '__main__':
my_graph: Graph[str] = {
'A': ['B', 'D'],
'B': ['A', 'C'],
'C': ['B', 'D'],
'D': ['C', 'A'],
}
assert all_edges(my_graph) == {('A', 'B'), ('A', 'D'), ('B', 'A'),
('B', 'C'), ('C', 'B'), ('C', 'D'),
('D', 'C'), ('D', 'A')}
assert all_edges(my_graph) == alt_all_edges(my_graph)
assert is_bidirected(my_graph)
assert not is_bidirected({'A': ['B', 'C'], 'B': [
'C'], 'C': ['A', 'B']})
my_graph = {
0: [1, 2, 3],
1: [0],
2: [3, 4, 0],
3: [0, 2],
4: [2],
5: [],
}
print(depth_first_search(my_graph, 5))

BIN
generics/graphen/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -0,0 +1,56 @@
# List Comprehensions
## Aufgabe 1 - Divisible by 7
Schreibe eine List-Comprehension welche eine Liste an Zahlen bis `n` zurückgibt, welche dividierbar durch 7 sind.
### Examples
```python
assert divisible_by_7(0) == []
assert divisible_by_7(10) == [7]
assert divisible_by_7(43) == [7, 14, 28, 35, 42]
```
---
## Aufgabe 2 - Contains 3
Schreibe eine List-Comprehension welche eine Liste an Zahlen bis `n` zurückgibt, welche eine `3` enthalten.
### Examples
```python
assert contains_3(10) == [3]
assert contains_3(24) == [3, 13, 23]
```
---
## Aufgabe 3 - Count Spaces
Schreibe eine Funktion, welche die Whitespaces (`' '`) in einem String zählt. Verwende hierzu List-Comprehensions.
### Examples
```python
assert count_spaces('') == 0
assert count_spaces(' ') == 1
assert count_spaces('hello, world ') == 2
```
---
## Aufgabe 4 - Remove Vowels
Schreibe eine Funktion, welche alle Vokale (`'AEIOUaeiou'`) aus einem String entfernt. Verwendet hierfür List-Comprehension
### Examples
```python
assert remove_vowels('') == ''
assert remove_vowels(' ') == ' '
assert remove_vowels('hello, world') == 'hll, wrld'
```
---
## Aufgabe 5 - Wortlängen
Schreibe eine Dictionary comprehension, welche ein Dictionary zurückgibt, wo die länge aller Wörter zu finden ist.
### Example
```python
assert word_length('this is a string') == {'this': 4, 'is': 2, 'a': 1, 'string': 6}
```
---
## Aufgabe 6 - Primzahlen generieren (schwer)
Schreibe eine Funktion, welche ein Liste an allen Primzahlen bis `n` zurückgibt. Verwende hierzu List-Comprehensions
### Examples
```python
assert prime_numbers(100) == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
```
---

View File

@@ -0,0 +1,36 @@
def divisible_by_7(n: int) -> list[int]:
"""
Returns a list of all numbers till 'n' that are divisible by '7'
"""
def contains_3(n: int) -> list[int]:
"""
Returns a list of all numbers that contain the digit '3' in them
"""
def count_spaces(string: str) -> int:
"""
Count the spaces in a string
"""
def remove_vowels(string: str) -> str:
"""
Remove all vowels from the string
"""
def word_lengths(string: str) -> dict[str, int]:
"""
Create a dictionary of all words with their lengths
"""
def prime_numbers(n: int) -> list[int]:
"""
Returns a list of all prime numbers till 'n' (HARD)
"""

View File

View File

@@ -0,0 +1,41 @@
def divisible_by_7(n: int) -> list[int]:
"""
Returns a list of all numbers till 'n' that are divisible by '7'
"""
return [x for x in range(1, n) if x % 7 == 0]
def contains_3(n: int) -> list[int]:
"""
Returns a list of all numbers that contain the digit '3' in them
"""
return [x for x in range(1, n) if '3' in str(x)]
def count_spaces(string: str) -> int:
"""
Count the spaces in a string
"""
return len([ch for ch in string if ' ' == ch])
def remove_vowels(string: str) -> str:
"""
Remove all vowels from the string
"""
return ''.join([ch for ch in string if ch.lower() not in 'aeiou'])
def word_lengths(string: str) -> dict[str, int]:
"""
Create a dictionary of all words with their lengths
"""
return {word: len(word) for word in string.split(' ') if len(word) > 0}
def prime_numbers(n: int) -> list[int]:
"""
Returns a list of all prime numbers till 'n' (HARD)
"""
return [x for x in range(2, n) if all(x % y != 0 for y in range(2, x))]

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
# [Primzahlen](./primes.py)
# [Primzahlen](src/branch/main/loops/primes/primes.py)
## `is_prime`
@@ -28,4 +28,4 @@ Schreiben Sie nun folgende Funktion `prime_factorize(n: int) -> list[int]` welch
Tipp: Sie können hierfür die Funktionen `is_prime` und `next_prime` verwenden
## [Hier gehts zu den Lösungen](./solution/primes.py)
## [Hier gehts zu den Lösungen](src/branch/main/loops/primes/solution)

84
loops/sort/README.md Normal file
View File

@@ -0,0 +1,84 @@
# [Sortierte Datenstrukturen](src/branch/main/loops/sort/sort.py)
## `selection_sort`
**Selection Sort** basiert auf dem Konzept durch jedes Element in einer Liste zu gehen um dann das kleineste Element in den übrigen zu suchen und mit dem aktuellen Element zu tauschen.
- Wichtig ist dass dabei die übergebene Liste nicht verändert wird und eine neue sortierte erstellt wird
- Manuell erstellen mit loop oder `deepcopy`
Beispiel:
```
arr[] = 64 25 12 22 11
// Find the minimum element in arr[0...4]
// and place it at beginning
11 25 12 22 64
// Find the minimum element in arr[1...4]
// and place it at beginning of arr[1...4]
11 12 25 22 64
// Find the minimum element in arr[2...4]
// and place it at beginning of arr[2...4]
11 12 22 25 64
// Find the minimum element in arr[3...4]
// and place it at beginning of arr[3...4]
11 12 22 25 64
```
<details>
<summary>Hilfe 1: Pseudocode</summary>
```
for e_i in xs
kleinstes_e = e_i
for e_j in xs mit: j = i + 1
if kleinstes_e > e_j
kleinstes_e = e_j
vertausche e_i und kleinstes_e
```
</details>
## `binary_search`
Hierbei handelt es sich um einen Algorithmus welcher die Position von einem Wert in einem sortierten Array schnell findet.
Beispiele:
```python
binary_search([1, 2, 3, 4, 5], 4) # 3, was der index von 4 ist
binary_search(["a", "b", "c", "d"], "b") # 1, was der index von "b" ist
```
- Dazu definieren wir eine linke Grenze `left` mit dem Anfangswert `0` und eine rechte Grenze `right` mit dem Anfangswert `len(xs) - 1`, damit können wir unser gesuchtes Element immer weiter eingrenzen.
- Jetzt wissen wir durch die Sortierung dass `left <= right` sein muss.
- Also gehen wir jetzt durch unsere Liste und berechnen die Mitte `middle` von `left` und `right` aus und vergleichen diese mit unserem gesuchten Wert.
- Ist `middle` kleiner als unser Wert dann können wir `left = middle + 1` setzen
- Ist `middle` größer als unser Wert dann können wir `right = middle - 1` setzen
- Sonst haben wir unseren Wert, nämlich `middle`, gefunden und können diesen zurückgeben
- Wenn der Wert nicht existiert wird `None` zurückgegeben
- Für leere Listen soll auch `None` zurückgegeben werden
<details>
<summary>Hilfe 1: Pseudocode</summary>
```
linker_index = 0
rechter_index = länge - 1
solange linker_index <= rechter_index dann
mitte = (linker_index + rechter_index) / 2
Wenn Liste[mitte] < gesuchter Wert dann
linker_index = mitte + 1
Wenn Liste[mitte] > gesuchter Wert dann
rechter_index = mitte - 1
Sonst
return Liste[mitte]
return Nichts
```
</details>
## [Hier gehts zur Lösung](src/branch/main/loops/sort/solution)

View File

@@ -0,0 +1,87 @@
# Solution
## `selection_sort`
Zunächst müssen wir eine neue Liste mit den selben Elementen erstellen, am schnellsten geht das mit `copy.deepcopy` welche eine vollständige Copie erstellt.
```python
from copy import deepcopy
xss = deepcopy(xs)
```
Wir müssen durch jedes Element gehen, also
```python
for i in range(len(xss)):
# ...
```
Nun wollen wir in den übrigen Elementen ($j = i + 1$) das kleinste finden
```python
for i in range(len(xss)):
min_i = i # index vom kleinsten Element,
# das aktuelle i falls dieses schon das kleinste ist
for j in range(i + 1, len(xss)):
# ...
```
Jetzt vergleichen wir nur noch jedes j-te Element mit dem aktuellen kleinsten
```python
for i in range(len(xss)):
min_i = i # index vom kleinsten Element,
# das aktuelle i falls dieses schon das kleinste ist
for j in range(i + 1, len(xss)):
if xss[j] < xss[min_i]:
min_i = j # kleineres element gefunden
```
Nun können wir einfach das i-te mit dem min_i vertauschen (entweder ist es i oder wir haben ein kleineres gefunden)
```python
xss[i], xss[min_i] = xss[min_i], xss[i] # tauschen i mit min_i
```
und am Ende können wir `xss` zurückgeben
```python
return xss
```
## `binary_search`
Hierfür definieren wir zunächst `left = 0` und `right = len(xs) - 1`, dann können wir die Schleifenbedingung definieren als
```python
while left <= right:
# ...
```
weil sobald diese nicht mehr gilt konnten wir den Wert nicht finden. (Links und Rechts tauschen) und wir können `None` zurückgeben.
Nun berechnen wir den mittleren Index mit dem integer division (oder auch floor weil es die Zahl hinterm Komma verwirft).
```python
middle = (left + right) // 2
```
Nun müssen wir nur noch `xs[middle]` mit `value` vergleichen
```python
if xs[middle] < value:
left = middle + 1
elif xs[middle] > value:
right = middle - 1
```
Und wenn `xs[middle] == value` ist haben wir unseren Index `middle` gefunden.
```python
if xs[middle] < value:
left = middle + 1
elif xs[middle] > value:
right = middle - 1
else:
return middle
```

View File

@@ -0,0 +1,27 @@
from copy import deepcopy
from typing import Iterator, Optional
def selection_sort[T](xs: Iterator[T]) -> Iterator[T]:
length = len(xs)
xss = deepcopy(xs)
for i in range(length):
min_i = i
for j in range(i + 1, length):
if xss[j] < xss[min_i]:
min_i = j
xss[i], xss[min_i] = xss[min_i], xss[i]
return xss
def binary_search[T](xs: list[T], value: T) -> Optional[int]:
left = 0
right = len(xs) - 1
while left <= right:
middle = (left + right) // 2
if xs[middle] < value:
left = middle + 1
elif xs[middle] > value:
right = middle - 1
else:
return middle
return None

9
loops/sort/sort.py Normal file
View File

@@ -0,0 +1,9 @@
from typing import Iterator, Optional
def selection_sort[T](xs: Iterator[T]) -> Iterator[T]:
return []
def binary_search[T](xs: list[T], value: T) -> Optional[int]:
return None

22
loops/sort/test_sort.py Normal file
View File

@@ -0,0 +1,22 @@
from random import randint
from typing import Iterator
from sort import selection_sort, binary_search
def get_random_collection(min: int, max: int, size: int) -> Iterator[int]:
return [randint(min, max) for _ in range(size)]
def test_selection_sort():
xs = [5, 4, 3, 2, 1, 0]
assert list(selection_sort(xs)) == sorted(xs)
assert xs == [5, 4, 3, 2, 1, 0], "list was modified in `selection_sort` return a copy instead"
xs = get_random_collection(0, 100, 100)
print(xs)
assert list(selection_sort(xs)) == sorted(xs)
def test_binary_search():
xs = sorted(set(get_random_collection(0, 10000, 100)))
for i, e in enumerate(xs):
assert binary_search(xs, e) == i
assert binary_search([], 1) == None
assert binary_search([2], 1) == None
assert binary_search([2, 3], 1) == None

View File

@@ -0,0 +1,95 @@
# Rekursive Datenstrukturen
## [LinkedList](./lists.py)
In der Vorlesung wurden uns rekursive Datenstrukturen vorgestellt. Und zwar in der einfachsten Form, einer Liste. Hierbei modellieren wir eine *Node* Datenstruktur die unseren Wert *Value* trägt und auf die nächste *Node* in der Liste verweist durch *next_node*.
```python
from dataclasses import dataclass
from typing import Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
```
Jetzt wollen wir eine Liste um unsere Node modellieren, hierbei interessiert uns nur die erste Node *__head* und die Länge *__length*.
```python
@dataclass
class LinkedList[T]:
def __post_init__(self):
self.__head: Optional[Node[T]] = None
self.__length: int = 0
```
Jetzt geht es darum alle Standardmethoden die eine Liste braucht zu implementieren.
## [BinaryTree](./trees.py)
Jetzt wollen wir eine andere rekursive Datenstruktur definieren, den binary Tree. Diese besteht immer aus zwei Abzweigungen *right* und *left*.
```python
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
```
Statt jetzt eine eigene Klasse zu erstellen die `Node` intern benutzt wollen wir einfach einen Typalias erstellen und imperative mit Funktionen statt Methoden arbeiten.
```python
type BinaryTree[T] = Optional[Node[T]]
```
### `traverse`
Man kann einen BinaryTree auf drei typische arten ablaufen
- Inorder
- Erst laufen wir Links ab
- Dann schauen wir uns die Node an
- Dann laufen wir Rechts ab
- Preorder
- Erst schauen wir uns die Node an
- Dann laufen wir Links ab
- Dann laufen wir Rechts ab
- Postorder
- Erst laufen wir Links ab
- Dann laufen wir Rechts ab
- Dann schauen wir uns die Node an
Hierfür erstellen wir einen `Enum` um zu unterscheiden wie wir gerade ablaufen
```python
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
```
Und jetzt implementieren wir einen Generator der uns einen Iterator erzeugt welcher den BinaryTree in der gegebenen Order abläuft.
```python
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
pass
```
Tipp:
Mit `yield from ...` kann man einen ganzen Iterator *yield*en.
```python
def my_range(start: int, end: int) -> Iterator[int]:
yield from range(start, end)
```
## [BinarySearchTree](./search_trees.py) (*Schwer*)
Was wäre wenn wir nun einen Binary Tree haben, dieser aber eine Sortierung hat. Wenn wir ein Element hinzufügen packen wir alle Elemente kleiner nach Links und alle anderen nach Rechts.
[Hier eine kleine Visualisierung](https://www.cs.usfca.edu/~galles/visualization/BST.html)
Probiert einfach mal rum damit ihr euch die Funktion eines BST vorstellen könnt. Wirklich komplex wird `delete`.

View File

@@ -0,0 +1,56 @@
from dataclasses import dataclass
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
@dataclass
class LinkedList[T]:
def __post_init__(self):
# unser erstes Element
self.__head: Optional[Node[T]] = None
# Die Länge der Liste
self.__length = 0
def __eq__(self, other: 'LinkedList[T]') -> bool:
"""Hier wollen wir alle Elemente von dieser Liste mit allen Elementen von einer anderen Liste vergleichen.
Hierbei kann man von ausgehen dass T vergleichbar ist.
Das Programm sollte nicht abstürzen bei unterschiedlichen längen etc. sondern einfach False zurückgeben"""
pass
def __iter__(self) -> Iterator[T]:
"""Hier wollen wir einen Iterator über alle Elemente haben, der Iterator kann leer sein
Tipp: Generator"""
pass
def append(self, value: T) -> None:
"""Hier wollen wir einfach `value` an das Ende der Liste setzen und die Länge erhöhen"""
pass
def remove(self, value: T):
"""Hier wollen wir den Wert `value` aus unserer Liste entfernen, wir können davon ausgehen dass T vergleichbar ist.
Wenn wir `value` nicht finden soll einfach nichts passieren"""
pass
def __len__(self) -> int:
"""Hier soll die Länge der Liste zurückgegeben werden"""
pass
def __getitem__(self, index: int) -> T:
"""_summary_
__getitem__ definiert das verhalten vom [] index operator wobei `index` der Wert ist der in [] übergeben wird
Hierbei sollen wir das Item `T` an Stelle `index` zurückgeben und sonst einen IndexError raisen
Args:
index (int): der Index von dem Item was wir suchen
Returns:
T: das Item an stelle `index`
"""
pass

View File

@@ -0,0 +1,13 @@
from typing import Optional
from trees import Node
type BinarySearchTree[T] = Optional[Node[T]]
def insert[T](node: BinarySearchTree[T], value: T) -> BinarySearchTree[T]:
pass
def exists[T](node: Optional[Node[T]], value: T) -> bool:
pass
def remove[T](node: BinarySearchTree[T], value: T) -> BinarySearchTree[T]:
pass

View File

@@ -0,0 +1,62 @@
from dataclasses import dataclass
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
@dataclass
class LinkedList[T]:
def __post_init__(self):
self.__head: Optional[Node[T]] = None
self.__length = 0
def __eq__(self, other: 'LinkedList[T]') -> bool:
if len(self) != len(other):
return False
for i in range(0, len(self)):
if self[i] != other[i]:
return False
return True
def __iter__(self) -> Iterator[T]:
node = self.__head
while node:
yield node.value
node = node.next_node
def append(self, value: T):
if not self.__head:
self.__head = Node(value, None)
else:
node = self.__head
while node.next_node:
node = node.next_node
node.next_node = Node(value, None)
self.__length += 1
def remove(self, value: T):
if self.__head and self.__head.value == value:
self.__head = self.__head.next_node
self.__length -= 1
elif self.__head:
node = self.__head
while node.next_node and node.next_node.value != value:
node = node.next_node
if node.next_node and node.next_node.value == value:
node.next_node = node.next_node.next_node
self.__length -= 1
def __len__(self) -> int:
return self.__length
def __getitem__(self, index: int) -> T:
if index >= len(self):
raise IndexError
node = self.__head
for _ in range(0, index):
node = node.next_node
return node.value

View File

@@ -0,0 +1,49 @@
from typing import Optional
from trees import Node
type BinarySearchTree[T] = Optional[Node[T]]
def insert[T](node: BinarySearchTree[T], value: T) -> BinarySearchTree[T]:
prev = None
curr = node
while curr:
prev = curr
if curr.value > value:
curr = curr.left
else:
curr = curr.right
if prev is None:
return Node(value, None, None)
elif value < prev.value:
prev.left = Node(value, None, None)
else:
prev.right = Node(value, None, None)
return node
def exists[T](node: Optional[Node[T]], value: T) -> bool:
return node and (exists(node.left, value)
if value < node.value
else (value == node.value or exists(node.right, value)))
def remove[T](node: BinarySearchTree[T], value: T) -> BinarySearchTree[T]:
if node is None:
return node
if value < node.value:
node.left = remove(node.left, value)
return node
if value > node.value:
node.right = remove(node.right, value)
return node
if node.right is None:
return node.left
if node.left is None:
return node.right
min_node = node.right
while min_node.left:
min_node = min_node.left
node.value = min_node.value
node.right = remove(node.right, min_node.value)
return node

View File

@@ -0,0 +1,77 @@
from lists import LinkedList
def test_append():
lst = LinkedList[int]()
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst2 = LinkedList[int]()
lst2.append(1)
lst2.append(2)
lst2.append(3)
lst2.append(4)
assert lst == lst2
def test_remove():
lst = LinkedList[int]()
lst.remove(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst.remove(2)
lst.remove(5)
lst2 = LinkedList[int]()
lst2.append(1)
lst2.append(3)
lst2.append(4)
assert lst == lst2
def test_length():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
assert len(lst) == 5
lst.remove(0)
assert len(lst) == 4
lst.remove(2)
assert len(lst) == 3
lst.remove(1)
assert len(lst) == 2
lst.remove(3)
assert len(lst) == 1
lst.remove(4)
assert len(lst) == 0
lst.remove(4)
lst.remove(5)
assert len(lst) == 0
def test_index():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
assert lst[0] == 0
assert lst[1] == 1
assert lst[2] == 2
assert lst[3] == 3
assert lst[4] == 4
def test_iter():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
j = 0
for i in iter(lst):
assert i == j
j += 1

View File

@@ -0,0 +1,51 @@
from typing import Iterator, Optional
from search_trees import BinarySearchTree, insert, remove, exists
from trees import Node
from random import randint as random
def traverse[T](tree: Optional[Node[T]]) -> Iterator[T]:
match tree:
case Node(value, left, right):
yield from traverse(left)
yield value
yield from traverse(right)
case _:
return
MAX = 100
MIN = 0
LENGTH = 100
NUMS = {random(MIN, MAX) for _ in range(0, LENGTH)}
NUMS_FILTER = {random(MIN, MAX) for _ in range(0, LENGTH // 4)}
def test_insert():
bst: BinarySearchTree = None
for num in NUMS:
bst = insert(bst, num)
assert list(traverse(bst)) == sorted(NUMS)
def test_remove():
bst: BinarySearchTree = None
for num in NUMS:
bst = insert(bst, num)
assert list(traverse(bst)) == sorted(NUMS)
for num in NUMS_FILTER:
remove(bst, num)
assert list(traverse(bst)) == sorted(filter(lambda x: x not in NUMS_FILTER, NUMS))
def test_exists():
bst: BinarySearchTree = None
for num in NUMS:
bst = insert(bst, num)
for num in NUMS_FILTER:
remove(bst, num)
assert all(map(lambda x: exists(bst, x),
filter(lambda x: x not in NUMS_FILTER, NUMS)))
assert all(map(lambda x: not exists(bst, x), NUMS_FILTER))

View File

@@ -0,0 +1,13 @@
from trees import Node, traverse, TraversalType
def test_traverse():
tree = Node(1, Node(2, Node(3, None, None), Node(4, Node(
5, None, None), None)), Node(6, Node(7, None, None), Node(8, None, None)))
assert ", ".join(map(lambda x: str(x), traverse(tree))
) == "3, 2, 5, 4, 1, 7, 6, 8"
assert ", ".join(map(lambda x: str(x), traverse(
tree, TraversalType.PREORDER))) == "1, 2, 3, 4, 5, 6, 7, 8"
assert ", ".join(map(lambda x: str(x), traverse(
tree, TraversalType.POSTORDER))) == "3, 5, 4, 2, 7, 8, 6, 1"

View File

@@ -0,0 +1,33 @@
from dataclasses import dataclass
from enum import Enum, auto
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
type BinaryTree[T] = Optional[Node[T]]
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
match (tree, order):
case (Node(value, left, right), TraversalType.INORDER):
yield from traverse(left, order)
yield value
yield from traverse(right, order)
case (Node(value, left, right), TraversalType.POSTORDER):
yield from traverse(left, order)
yield from traverse(right, order)
yield value
case (Node(value, left, right), TraversalType.PREORDER):
yield value
yield from traverse(left, order)
yield from traverse(right, order)
case _:
return

View File

@@ -0,0 +1,19 @@
from dataclasses import dataclass
from enum import Enum, auto
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
type BinaryTree[T] = Optional[Node[T]]
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
pass

View File

@@ -0,0 +1,92 @@
# Mit Zeichenketten arbeiten und manipulieren
Diese Teilaufgaben sind teilweise sehr einfach zu implementieren und sollen veranschaulichen was man alles mit Strings machen kann. Hierbei könnt ihr euch es natürlich einfach machen und die Buildins von `str` verwenden oder die Funktionen komplett selber implementieren.
## String-Class
Zunächst möchten wir eine *dataclass* *String* erstellen die genau einen *str* als Argument nimmt und in eimem privaten Attribut *__s* speichert. Dieses Attribut soll auch von Typ *str* sein und unsere *String* klasse intern representieren.
## Operatoren, Str, Len, und Iterator
Nun möchten wir alle standard Operatoren und Funktionen für unseren *String* überschreiben
### `__str__`
Diese Methode soll einfach die *str* Representation unserer *String* Klasse sein
### `__add__`, `__radd__`
Diese Methode soll unseren *String* nicht verändern, aber zwei *Str* konkatenieren. Hierbei kann ein *Str* sowohl ein *String* als auch ein *str* sein! Hierbei wird `self` mit `other` konkateniert.
`__radd__` hat genau das selbe verhalten, nur dass `other` mit `self` konkateniert wird.
```python
my_string = String("hello")
assert str(my_string + " world") == "hello world"
assert str("world " + my_str) == "world hello"
```
### `__len__`
Diese Methode soll uns die Länge unseres Strings zurückgeben
```python
assert len(String("hello world")) == 11
```
### `__eq__`
Diese Methode soll die Vergleichbarkeit zwischen *str* und *String* implementieren, also
```python
my_string = String("hello")
assert my_string == "hello"
assert my_string == String("hello")
```
### `__iter__`
Diese Methode soll einen Iterator aus unserem *String* machen damit wir über die einzelnen Characters iterieren können
```python
my_string = String("hello world")
my_it = iter(my_string)
for c in my_it:
print(c, end="") # hello world
```
## `concat`
Hierbei soll *self* nicht verändert werden, wir möchten *self* mit *other* konkatenieren und einen neuen konkatenierten *String* zurückgeben
## `contains`
Wir möchten schauen ob *other* in *self* enthalten ist
## `substring`
Wir möchten einen neuen *String* zurückgeben der alle Stellen von `start` bis `end`enthält. Hierbei sollten man von ausgehen dass `start` und `end` nicht negativ oder größer als der String sind.
## `strip`
Wir möchten am Anfang und Ende des Strings alle Stellen entfernen, solang diese in `chars` enthalten sind. Also
```python
assert String(" hello ").strip() == "hello"
```
## `replace`
Diese Methode soll überall `old` mit `new` genau `count`-Mal ersetzen. Wenn `count` negativ ist soll einfach jedes `old` ersetzt werden.
## `add_prefix`, `add_suffix`
`add_prefix` soll einen Prefix zu unserem String konkatenieren und analog dazu soll `add_suffix` einen Suffix zu unserem String konkatenieren
## `join`
Diese soll eine iterierbare Datenstruktur nehmen und zwischen jedes Element unseren *String* konkatenieren. Wir können davon ausgehen dass `T` zu einem *str* konvertiert werden kann (also `str(T)` anwendbar ist)
```python
assert String(", ").join([1, 2, 3, 4, 5]) == "1, 2, 3, 4, 5"
```

View File

@@ -0,0 +1,98 @@
from dataclasses import dataclass, InitVar
from typing import Iterator
type Str = 'String' | str
@dataclass
class String:
_s: InitVar[str]
def __post_init__(self, s: str) -> None:
self.__s = s
def __str__(self) -> str:
return self.__s
def __add__(self, other: Str) -> 'String':
return String(self.__s + str(other))
def __radd__(self, other: Str) -> 'String':
return String(str(other) + self.__s)
def __len__(self) -> int:
return len(self.__s)
def __eq__(self, other: Str) -> bool:
return str(self) == str(other)
def __iter__(self) -> Iterator[chr]:
pass
for i in range(0, len(self)):
yield chr(self.__s[i])
raise StopIteration
def concat(self, other: Str) -> 'String':
return self + other
def contains(self, other: Str) -> bool:
s = str(other)
for c in str(self):
if len(s) <= 0:
break
elif c == s[0]:
s: str = s[1:]
else:
s = str(other)
return len(s) == 0
def substring(self, start: int, end: int) -> 'String':
substr: str = ""
for i, c in enumerate(self.__s):
if i >= start and i <= end:
substr += c
return substr
def strip(self, chars: Str = ' ' + '\n' + '\t' + '\r') -> 'String':
i = 0
while i < len(self) and self.__s[i] in chars:
i += 1
j: int = len(self) - 1
while i <= j and self.__s[j] in chars:
j -= 1
return String(str(self)[i:j + 1])
def replace(self, old: Str, new: Str, count = -1) -> 'String':
o = str(old)
n = str(new)
new_str = self.__s
j = 0
while count > 0 or (count < 0 and j < len(new_str)):
i: int = j
while len(o) > 0 and j < len(new_str):
if o[0] == new_str[j]:
o: str = o[1:]
j += 1
else:
j += 1
break
if len(o) <= 0:
new_str = new_str[:i] + n + new_str[j:]
j += len(new) - len(old)
count -= 1
o = str(old)
return new_str
def add_prefix(self, prefix: Str) -> 'String':
return prefix + self
def add_suffix(self, suffix: Str) -> 'String':
return self + suffix
def join[T](self, xs: Iterator[T]) -> 'String':
output = String("")
length: int = len(xs)
for i, e in enumerate(xs):
output += str(e)
if i < length - 1:
output += self
return output

View File

@@ -0,0 +1,53 @@
from dataclasses import dataclass
from typing import Iterator
# kleine Hilfestellung mit `Str` kann man sowohl `str` als auch `String` meinen
type Str = 'String' | str
@dataclass
class String:
def __post_init__(self, s: str) -> None:
pass
def __str__(self) -> str:
pass
def __add__(self, other: Str) -> 'String':
pass
def __radd__(self, other: Str) -> 'String':
pass
def __len__(self, other: Str) -> int:
pass
def __eq__(self, other: Str) -> bool:
pass
def __iter__(self) -> Iterator[chr]:
pass
def concat(self, other: Str) -> 'String':
pass
def contains(self, other: Str) -> bool:
pass
def substring(self, start: int, end: int) -> 'String':
pass
def strip(self, chars: Str = ' ' + '\n' + '\t' + '\r') -> 'String':
pass
def replace(self, old: Str, new: Str, count = -1) -> 'String':
pass
def add_prefix(self, prefix: Str) -> 'String':
pass
def add_suffix(self, suffix: Str) -> 'String':
pass
def join[T](self, xs: Iterator[T]) -> 'String':
pass

View File

@@ -0,0 +1,46 @@
from strings import String
def test_eq_for_str():
test_str = String("this is a test string!")
assert test_str == "this is a test string!", "__eq__ wasn't implemented correctly for `String` and `str`"
def test_strings_contains():
test_str = String("this is a test")
assert test_str.contains("this")
assert not test_str.contains("release")
def test_strings_concat():
test_str = String("")
test_str += "test succesful!"
assert test_str == "test succesful!"
assert test_str.concat(" Or is it?") == "test succesful! Or is it?"
def test_strings_strip():
test_str = String(" halo? \n")
test_str = test_str.strip()
assert test_str == "halo?"
test_str = String(" \n ")
test_str = test_str.strip()
assert test_str == ""
def test_strings_replace():
test_str = String("har har har, try replacing thhis")
assert test_str.replace('har ', 'ha') == "hahahar, try replacing thhis"
test_str = test_str.replace('har ', 'ha')
assert test_str.replace('r', '', 1) == "hahaha, try replacing thhis"
test_str = test_str.replace('r', '', 1)
assert test_str.replace('hh', 'h') == "hahaha, try replacing this"
test_str = test_str.replace('hh', 'h')
assert test_str.replace('try replacing this', "replaced") == "hahaha, replaced"
def test_add_pre_suf():
test_str = String(" ")
assert test_str.add_suffix("suff") == " suff"
assert test_str.add_prefix("pref") == "pref "
assert test_str.add_suffix("suff").add_prefix("pref") == "pref suff"
def test_join():
assert String(", ").join([1, 2, 3, 4]) == "1, 2, 3, 4"
assert String(", ").join([]) == ""