Umstellen der Auswertungslogik

This commit is contained in:
Oliver Hofmann
2026-03-31 11:30:26 +02:00
parent c48b5c7e59
commit 1dfdff9391
25 changed files with 49 additions and 558 deletions
+3 -1
View File
@@ -1,4 +1,6 @@
from utils.algo_context import AlgoContext
from utils.algo_int import Int
from utils.algo_array import Array
from utils.algo_range import irange
from utils.algo_range import Range
from utils.algo_path import path
from utils.algo_priority_queue import PriorityQueue
+3 -3
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from random import randint
from utils.algo_context import AlgoContext, _NullContext, NULL_CTX
from utils.algo_int import Int
from utils.project_dir import get_path
from utils.algo_path import path
class Array:
@@ -130,8 +130,8 @@ class Array:
limit : int | None
Optionale Obergrenze für die Anzahl eingelesener Zeilen.
"""
path = get_path(filename)
with open(path) as f:
filepath = path(filename)
with open(filepath) as f:
lines = f.readlines()
if limit is not None:
lines = lines[:limit]
+1 -1
View File
@@ -117,7 +117,7 @@ class _NullContext:
"""
Kontext der alle Operationen stillschweigend ignoriert.
Wird intern von irange() verwendet, damit Schleifenindex-Arithmetik
Wird intern von Range() verwendet, damit Schleifenindex-Arithmetik
standardmäßig nicht mitgezählt wird.
__setattr__ ist absichtlich ein no-op: ``ctx.reads += 1`` bleibt wirkungslos.
+8
View File
@@ -212,6 +212,14 @@ class Int:
self._value //= other._value
return self
def __imod__(self, other):
other = self._wrap(other)
self._ctx.reads += 2
self._ctx.divisions += 1
self._ctx.writes += 1
self._value %= other._value
return self
# ------------------------------------------------------------------
# Bitoperationen (2 reads + 1 bitop + 1 write für in-place)
# ------------------------------------------------------------------
+8 -8
View File
@@ -3,14 +3,14 @@ from utils.algo_context import AlgoContext, NULL_CTX
from utils.algo_int import Int
def irange(start_or_stop, stop=None, step: int = 1, ctx: AlgoContext = None):
def Range(start_or_stop, stop=None, step: int = 1, ctx: AlgoContext = None):
"""
Drop-in Ersatz für range(), der Int-Objekte zurückgibt.
Wird wie Pythons range() aufgerufen:
irange(stop)
irange(start, stop)
irange(start, stop, step)
Range(stop)
Range(start, stop)
Range(start, stop, step)
Indexarithmetik und Zählung
---------------------------
@@ -20,14 +20,14 @@ def irange(start_or_stop, stop=None, step: int = 1, ctx: AlgoContext = None):
sollen in die Komplexitätsanalyse einfließen.
Mit ctx-Argument werden auch Indexoperationen gezählt:
for j in irange(n, ctx=ctx): ...
for j in Range(n, ctx=ctx): ...
Beispiel
--------
ctx = AlgoContext()
z = Array.random(10, 0, 99, ctx)
for i in irange(len(z) - 1): # i ist Int, Arithmetik nicht gezählt
if z[i] > z[i + 1]: # Vergleich gezählt (z-Zellen haben ctx)
for i in Range(len(z) - 1): # i ist Int, Arithmetik nicht gezählt
if z[i] > z[i + 1]: # Vergleich gezählt (z-Zellen haben ctx)
z.swap(i, i + 1)
"""
_ctx = ctx if ctx is not None else NULL_CTX
@@ -38,7 +38,7 @@ def irange(start_or_stop, stop=None, step: int = 1, ctx: AlgoContext = None):
start, stop_ = int(start_or_stop), int(stop)
step = int(step)
assert step != 0, "irange: step darf nicht 0 sein"
assert step != 0, "Range: step darf nicht 0 sein"
num = start
if step > 0:
-53
View File
@@ -1,53 +0,0 @@
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()
-40
View File
@@ -1,40 +0,0 @@
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 'task1'
print(pq.pop()) # Should print ('task2', 0)
print(pq.pop()) # Should print ('task3', 3)
-13
View File
@@ -1,13 +0,0 @@
from pathlib import Path
def get_path(filename) -> Path:
this_dir = Path(__file__).resolve().parent
project_dir = this_dir.parent
return project_dir / filename
if __name__ == "__main__":
filename = get_path("data/seq0.txt")
print(filename)
print(filename.resolve())
print(filename.is_file())
print(filename.exists())
-175
View File
@@ -1,175 +0,0 @@
import unittest
from utils.algo_context import AlgoContext
from utils.algo_int import Int
from utils.algo_array import Array
class TestArray(unittest.TestCase):
def setUp(self):
self.ctx = AlgoContext()
# ------------------------------------------------------------------
# Erzeugung
# ------------------------------------------------------------------
def test_from_list(self):
z = Array([3, 1, 4, 1, 5], self.ctx)
self.assertEqual(len(z), 5)
self.assertEqual(z[0].value, 3)
self.assertEqual(z[4].value, 5)
def test_random(self):
z = Array.random(20, 0, 99, self.ctx)
self.assertEqual(len(z), 20)
for cell in z:
self.assertGreaterEqual(cell.value, 0)
self.assertLessEqual(cell.value, 99)
def test_sorted(self):
z = Array.sorted(5, self.ctx)
values = [z[i].value for i in range(5)]
self.assertEqual(values, [0, 1, 2, 3, 4])
def test_from_file(self):
z = Array.from_file("data/seq0.txt", self.ctx)
self.assertGreater(len(z), 0)
def test_random_uses_no_write_counts(self):
"""Fabrikmethoden sollen keine Schreibzugriffe verzeichnen."""
z = Array.random(10, 0, 9, self.ctx)
self.assertEqual(self.ctx.writes, 0)
# ------------------------------------------------------------------
# Zugriff
# ------------------------------------------------------------------
def test_getitem_returns_int_cell(self):
z = Array([10, 20], self.ctx)
cell = z[0]
self.assertIsInstance(cell, Int)
self.assertEqual(cell.value, 10)
def test_getitem_with_int_index(self):
z = Array([10, 20, 30], self.ctx)
i = Int(2, self.ctx)
self.assertEqual(z[i].value, 30)
def test_getitem_no_read_count(self):
"""Array-Zugriff allein soll keinen read-Zähler erhöhen."""
z = Array([5, 6, 7], self.ctx)
_ = z[0]
self.assertEqual(self.ctx.reads, 0)
def test_setitem_plain_int(self):
z = Array([1, 2, 3], self.ctx)
z[0] = 99
self.assertEqual(z[0].value, 99)
self.assertEqual(self.ctx.writes, 1)
self.assertEqual(self.ctx.reads, 0)
def test_setitem_int_object(self):
z = Array([1, 2, 3], self.ctx)
v = Int(42, self.ctx)
z[1] = v
self.assertEqual(z[1].value, 42)
self.assertEqual(self.ctx.writes, 1)
self.assertEqual(self.ctx.reads, 1)
def test_setitem_cell_to_cell(self):
"""z[i] = z[j] kopiert den Wert (keine Alias-Referenz)."""
z = Array([10, 20], self.ctx)
z[0] = z[1]
self.assertEqual(z[0].value, 20)
# Wert ändern – z[1] darf sich nicht mitändern
z[0] = 99
self.assertEqual(z[1].value, 20)
# ------------------------------------------------------------------
# Swap
# ------------------------------------------------------------------
def test_swap_exchanges_values(self):
z = Array([1, 2, 3, 4, 5], self.ctx)
z.swap(0, 4)
self.assertEqual(z[0].value, 5)
self.assertEqual(z[4].value, 1)
def test_swap_counts_reads_and_writes(self):
z = Array([1, 2], self.ctx)
z.swap(0, 1)
self.assertEqual(self.ctx.reads, 2)
self.assertEqual(self.ctx.writes, 2)
def test_swap_with_int_indices(self):
z = Array([10, 20, 30], self.ctx)
i = Int(0, self.ctx)
j = Int(2, self.ctx)
z.swap(i, j)
self.assertEqual(z[0].value, 30)
self.assertEqual(z[2].value, 10)
# ------------------------------------------------------------------
# Vergleich über Array-Zellen (zählt beim Int, nicht beim Array)
# ------------------------------------------------------------------
def test_cell_comparison_counts_in_ctx(self):
z = Array([5, 3], self.ctx)
result = z[0] > z[1]
self.assertTrue(result)
self.assertEqual(self.ctx.comparisons, 1)
self.assertEqual(self.ctx.reads, 2)
# ------------------------------------------------------------------
# Iteration
# ------------------------------------------------------------------
def test_iteration(self):
z = Array([1, 2, 3], self.ctx)
values = [c.value for c in z]
self.assertEqual(values, [1, 2, 3])
# ------------------------------------------------------------------
# Länge
# ------------------------------------------------------------------
def test_len(self):
z = Array([1, 2, 3, 4], self.ctx)
self.assertEqual(len(z), 4)
def test_length_returns_int(self):
z = Array([1, 2, 3], self.ctx)
n = z.length()
self.assertIsInstance(n, Int)
self.assertEqual(n.value, 3)
class TestArrayIntegration(unittest.TestCase):
"""Bubble Sort als Integrationstest für das gesamte Framework."""
def test_bubble_sort_produces_correct_result(self):
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__),
'../vorlesung/L02_elementares_sortieren'))
from bubble_sorting import bubble_sort
ctx = AlgoContext()
z = Array([5, 3, 1, 4, 2], ctx)
bubble_sort(z, ctx)
values = [z[i].value for i in range(len(z))]
self.assertEqual(values, [1, 2, 3, 4, 5])
def test_bubble_sort_counts_comparisons(self):
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__),
'../vorlesung/L02_elementares_sortieren'))
from bubble_sorting import bubble_sort
ctx = AlgoContext()
z = Array([5, 4, 3, 2, 1], ctx) # worst case
bubble_sort(z, ctx)
# n=5: maximal n*(n-1)/2 = 10 Vergleiche
self.assertGreater(ctx.comparisons, 0)
self.assertLessEqual(ctx.comparisons, 10)
if __name__ == "__main__":
unittest.main()
-193
View File
@@ -1,193 +0,0 @@
import unittest
from utils.algo_context import AlgoContext
from utils.algo_int import Int
class TestInt(unittest.TestCase):
def setUp(self):
self.ctx = AlgoContext()
def _int(self, v):
return Int(v, self.ctx)
# ------------------------------------------------------------------
# Vergleiche
# ------------------------------------------------------------------
def test_comparison_counts_reads_and_compare(self):
a, b = self._int(5), self._int(3)
result = a > b
self.assertTrue(result)
self.assertEqual(self.ctx.comparisons, 1)
self.assertEqual(self.ctx.reads, 2)
def test_all_comparison_operators(self):
a, b = self._int(4), self._int(4)
self.assertTrue(a == b)
self.assertFalse(a != b)
self.assertTrue(a <= b)
self.assertTrue(a >= b)
self.assertFalse(a < b)
self.assertFalse(a > b)
self.assertEqual(self.ctx.comparisons, 6)
self.assertEqual(self.ctx.reads, 12)
def test_comparison_with_plain_int(self):
a = self._int(5)
result = a > 3 # 3 wird auto-gewrappt, kein extra Zähler
self.assertTrue(result)
self.assertEqual(self.ctx.comparisons, 1)
self.assertEqual(self.ctx.reads, 2)
def test_eq_with_none_returns_false(self):
a = self._int(1)
self.assertFalse(a == None) # noqa: E711
self.assertEqual(self.ctx.comparisons, 0) # keine Zählung
# ------------------------------------------------------------------
# Arithmetik (binär)
# ------------------------------------------------------------------
def test_addition_returns_new_int(self):
a, b = self._int(3), self._int(4)
c = a + b
self.assertIsInstance(c, Int)
self.assertEqual(c.value, 7)
self.assertEqual(self.ctx.additions, 1)
self.assertEqual(self.ctx.reads, 2)
self.assertEqual(self.ctx.writes, 0)
def test_subtraction(self):
a, b = self._int(10), self._int(4)
c = a - b
self.assertEqual(c.value, 6)
self.assertEqual(self.ctx.subtractions, 1)
def test_multiplication(self):
a, b = self._int(3), self._int(4)
c = a * b
self.assertEqual(c.value, 12)
self.assertEqual(self.ctx.multiplications, 1)
def test_floordiv(self):
a, b = self._int(10), self._int(3)
c = a // b
self.assertEqual(c.value, 3)
self.assertEqual(self.ctx.divisions, 1)
def test_mod(self):
a, b = self._int(10), self._int(3)
c = a % b
self.assertEqual(c.value, 1)
self.assertEqual(self.ctx.divisions, 1)
def test_arithmetic_with_plain_int(self):
a = self._int(5)
c = a + 3
self.assertEqual(c.value, 8)
self.assertEqual(self.ctx.additions, 1)
def test_result_shares_context(self):
a = self._int(5)
b = self._int(3)
c = a + b # c hat denselben ctx
_ = c > self._int(0) # Vergleich auf c zählt im selben ctx
self.assertEqual(self.ctx.comparisons, 1)
# ------------------------------------------------------------------
# Augmented assignment
# ------------------------------------------------------------------
def test_iadd_counts_read_add_write(self):
a, b = self._int(5), self._int(3)
a += b
self.assertEqual(a.value, 8)
self.assertEqual(self.ctx.reads, 2)
self.assertEqual(self.ctx.additions, 1)
self.assertEqual(self.ctx.writes, 1)
def test_isub(self):
a, b = self._int(10), self._int(4)
a -= b
self.assertEqual(a.value, 6)
self.assertEqual(self.ctx.subtractions, 1)
self.assertEqual(self.ctx.writes, 1)
def test_imul(self):
a, b = self._int(3), self._int(4)
a *= b
self.assertEqual(a.value, 12)
self.assertEqual(self.ctx.multiplications, 1)
self.assertEqual(self.ctx.writes, 1)
def test_iadd_with_plain_int(self):
a = self._int(5)
a += 1
self.assertEqual(a.value, 6)
self.assertEqual(self.ctx.writes, 1)
# ------------------------------------------------------------------
# set()
# ------------------------------------------------------------------
def test_set_plain_counts_one_write(self):
a = self._int(0)
a.set(42)
self.assertEqual(a.value, 42)
self.assertEqual(self.ctx.writes, 1)
self.assertEqual(self.ctx.reads, 0)
def test_set_int_counts_write_and_read(self):
a = self._int(0)
b = self._int(7)
a.set(b)
self.assertEqual(a.value, 7)
self.assertEqual(self.ctx.writes, 1)
self.assertEqual(self.ctx.reads, 1)
# ------------------------------------------------------------------
# Typkonvertierung
# ------------------------------------------------------------------
def test_int_conversion(self):
a = self._int(5)
self.assertEqual(int(a), 5)
def test_index_usage(self):
a = self._int(2)
lst = [10, 20, 30]
self.assertEqual(lst[a], 30) # __index__
def test_hash(self):
a = self._int(5)
self.assertEqual(hash(a), hash(5))
# ------------------------------------------------------------------
# AlgoContext.reset()
# ------------------------------------------------------------------
def test_context_reset(self):
a, b = self._int(3), self._int(4)
_ = a > b
self.ctx.reset()
self.assertEqual(self.ctx.comparisons, 0)
self.assertEqual(self.ctx.reads, 0)
class TestIntNullCtx(unittest.TestCase):
"""Int ohne expliziten ctx (irange-Verwendung) – keine Zählung."""
def test_arithmetic_without_ctx_produces_no_counts(self):
from utils.algo_context import NULL_CTX
a = Int(5) # uses NULL_CTX by default
b = Int(3)
_ = a + b
_ = a > b
# NULL_CTX hat feste 0-Attribute – keine Ausnahme, keine Zählung
self.assertEqual(NULL_CTX.additions, 0)
self.assertEqual(NULL_CTX.comparisons, 0)
if __name__ == "__main__":
unittest.main()