From fe533e6d4a2af0ba606cbdbc2d64c340b91673ed Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Tue, 31 Mar 2026 11:31:18 +0200 Subject: [PATCH] Umstellen der Auswertungslogik --- README.md | 157 +++++++++++++++++++++++++++++++++++ utils/algo_game.py | 53 ++++++++++++ utils/algo_path.py | 24 ++++++ utils/algo_priority_queue.py | 40 +++++++++ 4 files changed, 274 insertions(+) create mode 100644 README.md create mode 100644 utils/algo_game.py create mode 100644 utils/algo_path.py create mode 100644 utils/algo_priority_queue.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..69c032a --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# Algorithmen und Datenstrukturen – SoSe 26 + +Dieses Repository enthält den Code zur Vorlesung. Neben den Vorlesungsbeispielen +bietet es ein kleines Framework, mit dem ihr eure eigenen Algorithmen +**automatisch auf Operationen messen** könnt – ohne den Algorithmus selbst +anfassen zu müssen. + +--- + +## Wozu das Framework? + +In der Vorlesung analysieren wir Algorithmen theoretisch mit der O-Notation. +Das Framework erlaubt euch, diese Theorie **empirisch zu überprüfen**: Ihr +implementiert euren Algorithmus genauso wie in Python üblich – nur dass ihr +statt `list` und `int` die Wrapper-Typen `Array` und `Int` verwendet. Das +Framework zählt dann im Hintergrund automatisch Vergleiche, Lese- und +Schreibzugriffe sowie arithmetische Operationen. + +--- + +## Schnellstart + +### 1. Kontext anlegen + +```python +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_range import Range + +ctx = AlgoContext() +``` + +`AlgoContext` ist der Zähler. Ihr legt ihn einmal an und gebt ihn an eure +Datenstrukturen weiter. + +### 2. Array befüllen + +```python +z = Array.random(10, -100, 100, ctx) # 10 Zufallszahlen zwischen -100 und 100 +# oder: +z = Array([5, 3, 8, 1, 9, 2], ctx) # eigene Werte +``` + +### 3. Algorithmus schreiben + +Schreibt euren Algorithmus wie gewohnt. `Range` ist ein Drop-in-Ersatz für +`range` und liefert ebenfalls `Int`-Objekte – allerdings ohne Verbindung zum +Zähler. Dadurch wird **Indexarithmetik** (`j - 1`, `i + 1`) nicht mitgezählt, +während Operationen auf Array-*Inhalten* (die ja den echten `ctx` tragen) weiterhin +erfasst werden. Das entspricht der üblichen Konvention: gezählt werden nur +Operationen auf den Daten, nicht die Schleifensteuerung. + +```python +def insertion_sort(z: Array, ctx: AlgoContext): + for i in Range(1, len(z)): + elem = z[i] # liest z[i] (1 Lesezugriff) + j = int(i) - 1 + while j >= 0 and z[j] > elem: # vergleicht (1 Vergleich pro Schritt) + z[j + 1] = z[j] # schreibt (1 Schreibzugriff) + j -= 1 + z[j + 1] = elem +``` + +### 4. Ausführen und Ergebnis ablesen + +```python +insertion_sort(z, ctx) + +print(ctx.comparisons) # Anzahl Vergleiche +print(ctx.reads) # Lesezugriffe +print(ctx.writes) # Schreibzugriffe +print(ctx.additions) # Additionen +``` + +Oder kompakt: + +```python +print(ctx.summary()) +``` + +### 5. Komplexität über mehrere Eingabegrößen plotten + +```python +def analyze(sort_func, sizes): + ctx = AlgoContext() + for size in sizes: + ctx.reset() + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) + ctx.plot_stats(["comparisons", "writes"]) + +analyze(insertion_sort, range(10, 201, 10)) +``` + +Das öffnet ein Matplotlib-Fenster mit dem Verlauf der gezählten Operationen +über die Eingabegröße – ihr seht direkt, ob euer Algorithmus z.B. quadratisch oder +linear wächst. + +--- + +## Verfügbare Zähler + +| Attribut | Was wird gezählt | +|-----------------|-------------------------------------------| +| `comparisons` | `<`, `>`, `<=`, `>=`, `==`, `!=` | +| `reads` | Lesezugriffe auf `Int`-Werte | +| `writes` | Schreibzugriffe (Zuweisung, `swap`) | +| `additions` | `+`, `+=` | +| `subtractions` | `-`, `-=` | +| `multiplications` | `*`, `*=` | +| `divisions` | `/`, `//`, `/=` | +| `bitops` | `&`, `\|`, `^`, `<<`, `>>` | + +--- + +## Struktur des Repositories + +``` +utils/ Framework-Dateien (AlgoContext, Int, Array, Range) +vorlesung/ Vorlesungsbeispiele, geordnet nach Lektionsnummer +praktika/ Aufgabenstellungen der Praktika +playground/ Eure eigenen Abgaben und Experimente +data/ Beispieldatensätze +``` + +Die Vorlesungsbeispiele in `vorlesung/` sind vollständig mit dem Framework +instrumentiert und können als Vorlage für eigene Implementierungen dienen. + +## Datenpfade + +Die Demodaten in `data/` werden über `path()` aus `utils.algo_path` adressiert. +Die Funktion liefert immer den absoluten Pfad relativ zum Projektverzeichnis – +unabhängig davon, aus welchem Verzeichnis ihr das Skript startet oder welche IDE +ihr verwendet: + +```python +from utils.algo_path import path + +z = Array.from_file(path("data/seq0.txt"), ctx) +``` + +## PriorityQueue + +Für Graphalgorithmen (z.B. Dijkstra) steht eine fertige `PriorityQueue` bereit. +Sie basiert intern auf einem Heap – das Prinzip habt ihr in der Vorlesung zu +Heap Sort kennengelernt. Als Abkürzung dürft ihr die fertige Klasse verwenden: + +```python +from utils.algo_priority_queue import PriorityQueue + +pq = PriorityQueue() +pq.add_or_update("A", 0) # Knoten mit Priorität (Distanz) eintragen +pq.add_or_update("B", 5) +pq.add_or_update("B", 2) # Priorität aktualisieren +node, dist = pq.pop() # liefert ("A", 0) +``` diff --git a/utils/algo_game.py b/utils/algo_game.py new file mode 100644 index 0000000..cdb9dff --- /dev/null +++ b/utils/algo_game.py @@ -0,0 +1,53 @@ +import pygame + +class Game: + + def __init__(self, title, fps=60, size=(640, 400)): + self.title = title + self.fps = fps + self.size = size + self.clock = pygame.time.Clock() + self.dt = 0 + self.screen = None + + def init_game(self): + pygame.init() + pygame.display.set_caption(self.title) + self.screen = pygame.display.set_mode(self.size) + + + def game_loop(self): + while True: + # Berechnung der Zeitdifferenz seit dem letzten Frame + self.dt = self.clock.tick(self.fps) / 1000 + if self.event_handling() == False: + break + if self.update_game() == False: + break + self.draw_game() + + def exit_game(self): + pygame.quit() + + def event_handling(self): # bleibt in der Unterklasse unverändert + for event in pygame.event.get(): + if not self.handle_event(event): + return False + return True + + def handle_event(self, event): # wird in der Unterklasse überschrieben + if event.type == pygame.QUIT: + return False + return True + + def update_game(self): + return True + + def draw_game(self): + pygame.display.flip() + + def run(self): + self.init_game() + self.game_loop() + self.exit_game() + diff --git a/utils/algo_path.py b/utils/algo_path.py new file mode 100644 index 0000000..db22ebb --- /dev/null +++ b/utils/algo_path.py @@ -0,0 +1,24 @@ +from pathlib import Path + + +def path(filename) -> Path: + """Gibt den absoluten Pfad zu einer Datei im Projektverzeichnis zurück. + + Funktioniert unabhängig vom Arbeitsverzeichnis und der verwendeten IDE, + da der Pfad relativ zur Position dieses Moduls berechnet wird. + + Beispiel + -------- + from utils.algo_path import path + z = Array.from_file(path("data/seq0.txt"), ctx) + """ + project_dir = Path(__file__).resolve().parent.parent + return project_dir / filename + + +if __name__ == "__main__": + filename = path("data/seq0.txt") + print(filename) + print(filename.resolve()) + print(filename.is_file()) + print(filename.exists()) diff --git a/utils/algo_priority_queue.py b/utils/algo_priority_queue.py new file mode 100644 index 0000000..f897cc5 --- /dev/null +++ b/utils/algo_priority_queue.py @@ -0,0 +1,40 @@ +import heapq + +class PriorityQueue: + def __init__(self): + self.heap = [] + self.entry_finder = {} # map: item -> [priority, item] + self.REMOVED = '' + self.counter = 0 # unique sequence count to break ties + + def add_or_update(self, item, priority): + if item in self.entry_finder: + self.remove(item) + count = self.counter + entry = [priority, count, item] + self.entry_finder[item] = entry + heapq.heappush(self.heap, entry) + self.counter += 1 + + def remove(self, item): + entry = self.entry_finder.pop(item) + entry[-1] = self.REMOVED # mark as removed + + def pop(self): + while self.heap: + priority, count, item = heapq.heappop(self.heap) + if item != self.REMOVED: + del self.entry_finder[item] + return item, priority + return None + +if __name__ == "__main__": + pq = PriorityQueue() + pq.add_or_update('task1', 1) + pq.add_or_update('task2', float('inf')) + pq.add_or_update('task3', float('inf')) + + print(pq.pop()) # Should print ('task1', 1) + pq.add_or_update('task2', 0) # Update priority of 'task2' + print(pq.pop()) # Should print ('task2', 0) + print(pq.pop()) # Should print ('task3', 3) \ No newline at end of file