Umstellen der Auswertungslogik

This commit is contained in:
Oliver Hofmann 2026-03-31 11:31:18 +02:00
parent 1dfdff9391
commit fe533e6d4a
4 changed files with 274 additions and 0 deletions

157
README.md Normal file
View File

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

53
utils/algo_game.py Normal file
View File

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

24
utils/algo_path.py Normal file
View File

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

View File

@ -0,0 +1,40 @@
import heapq
class PriorityQueue:
def __init__(self):
self.heap = []
self.entry_finder = {} # map: item -> [priority, item]
self.REMOVED = '<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)