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