Compare commits
18 Commits
f4a0576f3c
...
main
Author | SHA1 | Date | |
---|---|---|---|
1efd3b66b0 | |||
483c6ee562 | |||
6f733ce98e | |||
c83f33cfca | |||
c7c1f3ea98 | |||
4c5d27e8d1 | |||
36b6a78833 | |||
14cd9fe8b0 | |||
1121c6a171 | |||
9ea341183d | |||
801b641fb2 | |||
f3e49c96cd | |||
f89435117a | |||
56c0d4bec3 | |||
407389a5cb | |||
da878ec3e6 | |||
8ed82e01aa | |||
23f4c7be52 |
27
README.md
27
README.md
@@ -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
|
## Reihenfolge der Themen
|
||||||
|
|
||||||
Es gibt keine direkte Reihenfolge, lediglich Themen die sich teilweise überschneiden. Dennoch gibt es eine Reihenfolge nach Wichtigkeit 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
|
- allgemeine Knobelaufgaben rund um Schleifen
|
||||||
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
|
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
|
||||||
- [Zeichenketten (Strings `str`)](./strings)
|
- [Zeichenketten (Strings `str`)](src/branch/main/strings)
|
||||||
- allgemeine Knobelaufgaben rund um `str`
|
- allgemeine Knobelaufgaben rund um `str`
|
||||||
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
|
- Einfach mit ein paar schwierigeren Aufgaben zum Nachdenken
|
||||||
- [Dataclasses (OOP `@dataclass`)](./dataclasses)
|
- [Dataclasses (OOP `@dataclass`)](src/branch/main/dataclasses)
|
||||||
- Objekt orientierte Programmierung mit `@dataclass`
|
- Objekt orientierte Programmierung mit `@dataclass`
|
||||||
- Einfach (Auswendig lernen)
|
- Einfach (Auswendig lernen)
|
||||||
- [Pattern Matching (`match`)](./pattern_matching)
|
- [Pattern Matching (`match`)](src/branch/main/pattern_matching)
|
||||||
- Intensive Übungen zu `match`
|
- Intensive Übungen zu `match`
|
||||||
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
|
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
|
||||||
- [Typvariabeln (Generics `[T]`)](./generics)
|
- [Typvariabeln (Generics `[T]`)](src/branch/main/generics)
|
||||||
- Platzhalter Variabeln um generische Typannotation umzusetzen
|
- Platzhalter Variabeln um generische Typannotation umzusetzen
|
||||||
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
|
- Mittel (Auswendig lernen, aber erfordert grundlegende Konzepte)
|
||||||
- [Rekursion (Tree)](./recursion)
|
- [Rekursion (Tree)](src/branch/main/recursion)
|
||||||
- Sich selbst aufrufende Funktionen
|
- Sich selbst aufrufende Funktionen
|
||||||
- Schwer, da das Konzept etwas verwirrend ist, aber gut für schnelle Punkte in der Klausur!
|
- 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!
|
- Erzeugen von Iteratoren auf die seltsame Python Art und Weise!
|
||||||
- Mittel, da das Konzept etwas seltsam ist. Muss man einfach ein paar mal machen!
|
- 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!
|
- Programmieren-Paradigma bei dem der Programmfluss durch Funktionen bestimmt wird!
|
||||||
- Schwer, da das Konzept etwas schwer zu verstehen ist und viele Grundlagen vorraussetzt
|
- 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
|
- Zunächst braucht Ihr `pytest` welches ihr mit `pip install -m pytest` installieren könnt
|
||||||
- Könnt auch gerne nachfragen wenn was nicht funktioniert!
|
- Könnt auch gerne nachfragen wenn was nicht funktioniert!
|
||||||
- Dann könnt ihr einfach die Tests mit `pytest` in der Konsole aufrufen
|
- Dann könnt ihr einfach die Tests mit `pytest` in der Konsole aufrufen
|
||||||
- Schlagen die Tests fehl sieht das so aus:
|
|
||||||

|
|
||||||
- Hier sagt euch Pytest auch was alles nicht an eurem Code funktioniert
|
|
||||||
- Funktioniert euer Code sieht das so aus:
|
|
||||||

|
|
||||||
|
|
||||||
## Kontakt
|
## Kontakt
|
||||||
|
|
||||||
@@ -59,4 +54,4 @@ Es gibt keine direkte Reihenfolge, lediglich Themen die sich teilweise überschn
|
|||||||
- Daniel Mironow (Tutor)
|
- Daniel Mironow (Tutor)
|
||||||
- [E-Mail](mailto:mail@danielmironov.dev)
|
- [E-Mail](mailto:mail@danielmironov.dev)
|
||||||
- [Discord](https://discord.com/users/236939658301407243)
|
- [Discord](https://discord.com/users/236939658301407243)
|
||||||
- [Discord-Server](https://discord.gg/naeprrX7hB)
|
- [Discord-Server](https://discord.gg/naeprrX7hB)
|
||||||
|
1
generator/primes/README.md
Normal file
1
generator/primes/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# primes generator
|
0
generator/primes/primes.py
Normal file
0
generator/primes/primes.py
Normal file
29
generator/primes/solution/primes.py
Normal file
29
generator/primes/solution/primes.py
Normal 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
|
51
generator/primes/solution/test_primes.py
Normal file
51
generator/primes/solution/test_primes.py
Normal file
File diff suppressed because one or more lines are too long
34
generics/graphen/README.md
Normal file
34
generics/graphen/README.md
Normal 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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'],
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
60
generics/graphen/graphs.py
Normal file
60
generics/graphen/graphs.py
Normal 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
BIN
generics/graphen/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
56
list_comprehensions/README.md
Normal file
56
list_comprehensions/README.md
Normal 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]
|
||||||
|
```
|
||||||
|
---
|
36
list_comprehensions/comprehensions.py
Normal file
36
list_comprehensions/comprehensions.py
Normal 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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
0
list_comprehensions/solution/README.md
Normal file
0
list_comprehensions/solution/README.md
Normal file
41
list_comprehensions/solution/comprehensions.py
Normal file
41
list_comprehensions/solution/comprehensions.py
Normal 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))]
|
||||||
|
|
77
list_comprehensions/test_comprehensions.py
Normal file
77
list_comprehensions/test_comprehensions.py
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
# [Primzahlen](./primes.py)
|
# [Primzahlen](src/branch/main/loops/primes/primes.py)
|
||||||
|
|
||||||
## `is_prime`
|
## `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
|
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
84
loops/sort/README.md
Normal 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)
|
87
loops/sort/solution/README.md
Normal file
87
loops/sort/solution/README.md
Normal 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
|
||||||
|
```
|
27
loops/sort/solution/sort.py
Normal file
27
loops/sort/solution/sort.py
Normal 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
9
loops/sort/sort.py
Normal 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
22
loops/sort/test_sort.py
Normal 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
|
95
recursion/recursive_datastructure/README.md
Normal file
95
recursion/recursive_datastructure/README.md
Normal 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`.
|
56
recursion/recursive_datastructure/lists.py
Normal file
56
recursion/recursive_datastructure/lists.py
Normal 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
|
13
recursion/recursive_datastructure/search_trees.py
Normal file
13
recursion/recursive_datastructure/search_trees.py
Normal 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
|
62
recursion/recursive_datastructure/solution/lists.py
Normal file
62
recursion/recursive_datastructure/solution/lists.py
Normal 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
|
49
recursion/recursive_datastructure/solution/search_trees.py
Normal file
49
recursion/recursive_datastructure/solution/search_trees.py
Normal 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
|
77
recursion/recursive_datastructure/solution/test_lists.py
Normal file
77
recursion/recursive_datastructure/solution/test_lists.py
Normal 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
|
@@ -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))
|
13
recursion/recursive_datastructure/solution/test_trees.py
Normal file
13
recursion/recursive_datastructure/solution/test_trees.py
Normal 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"
|
33
recursion/recursive_datastructure/solution/trees.py
Normal file
33
recursion/recursive_datastructure/solution/trees.py
Normal 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
|
19
recursion/recursive_datastructure/trees.py
Normal file
19
recursion/recursive_datastructure/trees.py
Normal 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
|
92
strings/string_manipulation/README.md
Normal file
92
strings/string_manipulation/README.md
Normal 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"
|
||||||
|
```
|
98
strings/string_manipulation/solution/strings.py
Normal file
98
strings/string_manipulation/solution/strings.py
Normal 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
|
53
strings/string_manipulation/strings.py
Normal file
53
strings/string_manipulation/strings.py
Normal 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
|
46
strings/string_manipulation/test_strings.py
Normal file
46
strings/string_manipulation/test_strings.py
Normal 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([]) == ""
|
Reference in New Issue
Block a user