Umstellen der Auswertungslogik
This commit is contained in:
parent
1dfdff9391
commit
fe533e6d4a
157
README.md
Normal file
157
README.md
Normal 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
53
utils/algo_game.py
Normal 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
24
utils/algo_path.py
Normal 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())
|
||||
40
utils/algo_priority_queue.py
Normal file
40
utils/algo_priority_queue.py
Normal 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)
|
||||
Loading…
x
Reference in New Issue
Block a user