diff --git a/.gitignore b/.gitignore index 9f4482b..0645b51 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ build/ # Claude Code metadata CLAUDE.md .claude/ +specs/ # OS .DS_Store diff --git a/utils/__init__.py b/utils/__init__.py index e69de29..802c781 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -0,0 +1,4 @@ +from utils.algo_context import AlgoContext +from utils.algo_int import Int +from utils.algo_array import Array +from utils.algo_range import irange diff --git a/utils/algo_array.py b/utils/algo_array.py new file mode 100644 index 0000000..dc18867 --- /dev/null +++ b/utils/algo_array.py @@ -0,0 +1,139 @@ +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 + + +class Array: + """ + Instrumentiertes Array. + + Jede Zelle ist ein Int-Objekt, das Operationen an den gemeinsamen + AlgoContext meldet. + + Zugriff + ------- + arr[i] gibt die Int-Zelle zurück (kein Zähler) + arr[i] = v setzt den Wert der Zelle (1 write, +1 read falls v ein Int) + len(arr) gibt plain int zurück + arr.length() gibt Int zurück (für Algorithmen nützlich) + arr.swap(i,j) tauscht zwei Elemente (2 reads + 2 writes) + + Hinweis: arr[i] gibt eine Referenz auf die Zelle zurück. + Lesende Verwendung (Vergleich, Arithmetik) zählt dort – nicht beim Zugriff selbst. + + Fabrikmethoden + -------------- + Array.random(n, min_val, max_val, ctx) zufällige Werte + Array.sorted(n, ctx) aufsteigend sortiert 0..n-1 + Array.from_file(filename, ctx) Werte aus Textdatei (eine Zahl pro Zeile) + + Beispiel + -------- + ctx = AlgoContext() + z = Array.random(10, 0, 99, ctx) + if z[0] > z[1]: # 2 reads, 1 comparison + z.swap(0, 1) # 2 reads, 2 writes + """ + + def __init__(self, data: list, ctx: AlgoContext | _NullContext): + self._ctx = ctx + self._cells = [Int(v, ctx) for v in data] + + # ------------------------------------------------------------------ + # Element-Zugriff + # ------------------------------------------------------------------ + + def __getitem__(self, index) -> Int: + """Gibt die Int-Zelle zurück. Kein Zähler – Zählung erfolgt bei Nutzung.""" + return self._cells[int(index)] + + def __setitem__(self, index, value): + """ + Setzt den Wert der Zelle. + + Delegiert an Int.set() → 1 write (+1 read falls value ein Int ist). + """ + self._cells[int(index)].set(value) + + # ------------------------------------------------------------------ + # Länge + # ------------------------------------------------------------------ + + def __len__(self) -> int: + return len(self._cells) + + def length(self) -> Int: + """Gibt die Länge als Int zurück (für Algorithmen, die mit Int rechnen).""" + return Int(len(self._cells), self._ctx) + + # ------------------------------------------------------------------ + # Iteration + # ------------------------------------------------------------------ + + def __iter__(self): + return iter(self._cells) + + # ------------------------------------------------------------------ + # Tausch + # ------------------------------------------------------------------ + + def swap(self, i, j): + """ + Tauscht die Werte an Position i und j. + + Zählt: 2 reads + 2 writes. + """ + ci = self._cells[int(i)] + cj = self._cells[int(j)] + self._ctx.reads += 2 + self._ctx.writes += 2 + ci._value, cj._value = cj._value, ci._value + + # ------------------------------------------------------------------ + # Darstellung + # ------------------------------------------------------------------ + + def __str__(self): + return '[' + ', '.join(str(c) for c in self._cells) + ']' + + def __repr__(self): + return f"Array({[c.value for c in self._cells]})" + + # ------------------------------------------------------------------ + # Fabrikmethoden + # ------------------------------------------------------------------ + + @staticmethod + def random(n: int, min_val: int, max_val: int, ctx: AlgoContext) -> Array: + """Erzeugt ein Array mit n zufälligen Werten aus [min_val, max_val].""" + n = int(n) + return Array([randint(min_val, max_val) for _ in range(n)], ctx) + + @staticmethod + def sorted(n: int, ctx: AlgoContext) -> Array: + """Erzeugt ein aufsteigend sortiertes Array 0, 1, …, n-1.""" + n = int(n) + return Array(list(range(n)), ctx) + + @staticmethod + def from_file(filename: str, ctx: AlgoContext, limit: int | None = None) -> Array: + """ + Liest Ganzzahlen aus einer Textdatei (eine Zahl pro Zeile). + + Parameters + ---------- + filename : str + Pfad relativ zum Projektverzeichnis oder absolut. + ctx : AlgoContext + limit : int | None + Optionale Obergrenze für die Anzahl eingelesener Zeilen. + """ + path = get_path(filename) + with open(path) as f: + lines = f.readlines() + if limit is not None: + lines = lines[:limit] + data = [int(line.strip()) for line in lines if line.strip()] + return Array(data, ctx) diff --git a/utils/algo_context.py b/utils/algo_context.py new file mode 100644 index 0000000..0326d19 --- /dev/null +++ b/utils/algo_context.py @@ -0,0 +1,135 @@ +import matplotlib.pyplot as plt + + +class AlgoContext: + """ + Kontext für die Instrumentierung von Algorithmen. + + Jeder Algorithmus erhält eine eigene Instanz – kein globaler Zustand. + Die Zähler werden von Int- und Array-Operationen automatisch erhöht. + + Zähler + ------ + reads Lesezugriffe (bei Vergleichen und Arithmetik je Operand) + writes Schreibzugriffe (set, Zuweisung, augmented assignment) + comparisons Vergleiche (<, <=, >, >=, ==, !=) + additions Additionen (+, +=) + subtractions Subtraktionen (-, -=) + multiplications Multiplikationen (*, *=) + divisions Divisionen (/, //, %, /=, //=) + bitops Bitoperationen (&, |, ^, <<, >>) + + Beispiel + -------- + ctx = AlgoContext() + z = Array.random(20, -100, 100, ctx) + bubble_sort(z, ctx) + print(ctx) + ctx.save_stats(20) + """ + + def __init__(self): + self.reads = 0 + self.writes = 0 + self.comparisons = 0 + self.additions = 0 + self.subtractions = 0 + self.multiplications = 0 + self.divisions = 0 + self.bitops = 0 + self._stats: dict[int, dict] = {} + + def reset(self): + """Setzt alle Zähler auf 0 zurück.""" + self.reads = 0 + self.writes = 0 + self.comparisons = 0 + self.additions = 0 + self.subtractions = 0 + self.multiplications = 0 + self.divisions = 0 + self.bitops = 0 + + def _snapshot(self) -> dict: + return { + "reads": self.reads, + "writes": self.writes, + "comparisons": self.comparisons, + "additions": self.additions, + "subtractions": self.subtractions, + "multiplications": self.multiplications, + "divisions": self.divisions, + "bitops": self.bitops, + } + + def save_stats(self, n: int): + """Speichert einen Schnappschuss der aktuellen Zähler für Eingabegröße n.""" + self._stats[n] = self._snapshot() + + def plot_stats(self, labels: list[str]): + """ + Zeichnet die gespeicherten Statistiken als Liniendiagramm. + + Parameters + ---------- + labels : list[str] + Zähler-Namen, z.B. ["comparisons", "writes"] + """ + data = self._stats + x = list(data.keys()) + + fig, axes = plt.subplots(len(labels), 1, figsize=(8, 4 * len(labels)), sharex=True) + if len(labels) == 1: + axes = [axes] + + for ax, label in zip(axes, labels): + y = [data[k][label] for k in x] + ax.plot(x, y, label=label) + ax.set_ylabel(label) + ax.legend() + + plt.xlabel("n") + plt.tight_layout() + plt.show() + + def summary(self) -> str: + """Gibt alle Zähler als formatierten Text zurück.""" + return ( + f"Reads: {self.reads}\n" + f"Writes: {self.writes}\n" + f"Comparisons: {self.comparisons}\n" + f"Additions: {self.additions}\n" + f"Subtractions: {self.subtractions}\n" + f"Multiplications: {self.multiplications}\n" + f"Divisions: {self.divisions}\n" + f"Bitwise ops: {self.bitops}" + ) + + def __str__(self): + return self.summary() + + def __repr__(self): + return (f"AlgoContext(reads={self.reads}, writes={self.writes}, " + f"comparisons={self.comparisons})") + + +class _NullContext: + """ + Kontext der alle Operationen stillschweigend ignoriert. + + Wird intern von irange() verwendet, damit Schleifenindex-Arithmetik + standardmäßig nicht mitgezählt wird. + + __setattr__ ist absichtlich ein no-op: ``ctx.reads += 1`` bleibt wirkungslos. + """ + reads = writes = comparisons = 0 + additions = subtractions = multiplications = divisions = bitops = 0 + + def __setattr__(self, name, value): + pass # alle Schreibzugriffe ignorieren + + def save_stats(self, n): pass + def reset(self): pass + + +NULL_CTX = _NullContext() diff --git a/utils/algo_int.py b/utils/algo_int.py new file mode 100644 index 0000000..e5905e1 --- /dev/null +++ b/utils/algo_int.py @@ -0,0 +1,319 @@ +from __future__ import annotations +from utils.algo_context import AlgoContext, _NullContext, NULL_CTX + + +class Int: + """ + Instrumentierter Integer-Typ. + + Alle Operationen werden im zugehörigen AlgoContext gezählt. + Der Rohwert ist über das Attribut ``value`` direkt lesbar (kein Zähler), + was für Visualisierungen benötigt wird. + + Zählregeln + ---------- + Vergleich (a < b): 2 reads + 1 comparison + Arithmetik (a + b): 2 reads + 1 arithmetische Operation → neues Int + Augmented (a += b): 2 reads + 1 arithmetische Operation + 1 write + set(v): 1 write (+1 read falls v ein Int ist) + + Auto-Wrapping + ------------- + Alle Operatoren akzeptieren auch plain-Python-Werte (int, float). + Diese werden intern zu Int(v, NULL_CTX) gewrappt, ohne Zähler zu erhöhen. + + Beispiel + -------- + ctx = AlgoContext() + a = Int(5, ctx) + b = Int(3, ctx) + if a > b: # 2 reads, 1 comparison + a += b # 2 reads, 1 addition, 1 write + print(a.value) # 8 (kein Zähler) + """ + + def __init__(self, value, ctx: AlgoContext | _NullContext = NULL_CTX): + if isinstance(value, Int): + self._value = value._value + else: + self._value = value + self._ctx = ctx + + # ------------------------------------------------------------------ + # Rohwert-Zugriff (für Visualisierung, kein Zähler) + # ------------------------------------------------------------------ + + @property + def value(self): + """Rohwert für Visualisierung – wird nicht gezählt.""" + return self._value + + # ------------------------------------------------------------------ + # Schreiben + # ------------------------------------------------------------------ + + def set(self, new_value): + """ + Setzt den Wert. + + Zählt: 1 write (+1 read falls new_value ein Int ist) + """ + self._ctx.writes += 1 + if isinstance(new_value, Int): + self._ctx.reads += 1 + self._value = new_value._value + else: + self._value = new_value + + # ------------------------------------------------------------------ + # Interner Hilfshelfer + # ------------------------------------------------------------------ + + def _wrap(self, other) -> Int: + """Wraps plain Python-Wert zu Int mit NULL_CTX (kein Zähler).""" + if isinstance(other, Int): + return other + return Int(other, NULL_CTX) + + # ------------------------------------------------------------------ + # Vergleiche (2 reads + 1 comparison je Operation) + # ------------------------------------------------------------------ + + def __eq__(self, other): + if other is None: + return False + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value == other._value + + def __ne__(self, other): + if other is None: + return True + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value != other._value + + def __lt__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value < other._value + + def __le__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value <= other._value + + def __gt__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value > other._value + + def __ge__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.comparisons += 1 + return self._value >= other._value + + # ------------------------------------------------------------------ + # Arithmetik (2 reads + 1 op → neues Int mit gleichem ctx) + # ------------------------------------------------------------------ + + def __add__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.additions += 1 + return Int(self._value + other._value, self._ctx) + + def __radd__(self, other): + return self._wrap(other).__add__(self) + + def __sub__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.subtractions += 1 + return Int(self._value - other._value, self._ctx) + + def __rsub__(self, other): + return self._wrap(other).__sub__(self) + + def __mul__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.multiplications += 1 + return Int(self._value * other._value, self._ctx) + + def __rmul__(self, other): + return self._wrap(other).__mul__(self) + + def __truediv__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.divisions += 1 + return Int(self._value / other._value, self._ctx) + + def __floordiv__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.divisions += 1 + return Int(self._value // other._value, self._ctx) + + def __mod__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.divisions += 1 + return Int(self._value % other._value, self._ctx) + + # ------------------------------------------------------------------ + # Augmented assignment (2 reads + 1 op + 1 write, in-place) + # ------------------------------------------------------------------ + + def __iadd__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.additions += 1 + self._ctx.writes += 1 + self._value += other._value + return self + + def __isub__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.subtractions += 1 + self._ctx.writes += 1 + self._value -= other._value + return self + + def __imul__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.multiplications += 1 + self._ctx.writes += 1 + self._value *= other._value + return self + + def __itruediv__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.divisions += 1 + self._ctx.writes += 1 + self._value /= other._value + return self + + def __ifloordiv__(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) + # ------------------------------------------------------------------ + + def __and__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + return Int(self._value & other._value, self._ctx) + + def __or__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + return Int(self._value | other._value, self._ctx) + + def __xor__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + return Int(self._value ^ other._value, self._ctx) + + def __lshift__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + return Int(self._value << other._value, self._ctx) + + def __rshift__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + return Int(self._value >> other._value, self._ctx) + + def __iand__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + self._ctx.writes += 1 + self._value &= other._value + return self + + def __ior__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + self._ctx.writes += 1 + self._value |= other._value + return self + + def __ixor__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + self._ctx.writes += 1 + self._value ^= other._value + return self + + def __ilshift__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + self._ctx.writes += 1 + self._value <<= other._value + return self + + def __irshift__(self, other): + other = self._wrap(other) + self._ctx.reads += 2 + self._ctx.bitops += 1 + self._ctx.writes += 1 + self._value >>= other._value + return self + + # ------------------------------------------------------------------ + # Typkonvertierung und Darstellung + # ------------------------------------------------------------------ + + def __int__(self): + return int(self._value) + + def __float__(self): + return float(self._value) + + def __index__(self): + """Ermöglicht Verwendung als Listen-Index (z.B. arr[i]).""" + return int(self._value) + + def __hash__(self): + return hash(self._value) + + def __bool__(self): + return bool(self._value) + + def __neg__(self): + return Int(-self._value, self._ctx) + + def __abs__(self): + return Int(abs(self._value), self._ctx) + + def __str__(self): + return str(self._value) + + def __repr__(self): + return f"Int({self._value})" diff --git a/utils/algo_range.py b/utils/algo_range.py new file mode 100644 index 0000000..c16c4bf --- /dev/null +++ b/utils/algo_range.py @@ -0,0 +1,51 @@ +from __future__ import annotations +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): + """ + 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) + + Indexarithmetik und Zählung + --------------------------- + Ohne ctx-Argument (Standard) erhalten die erzeugten Indices einen NULL_CTX – + Arithmetik auf Loop-Indices (z.B. ``j - 1``) wird dann **nicht** gezählt. + Das entspricht dem üblichen Lehrbuchwunsch: nur Operationen auf Array-Inhalten + sollen in die Komplexitätsanalyse einfließen. + + Mit ctx-Argument werden auch Indexoperationen gezählt: + for j in irange(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) + z.swap(i, i + 1) + """ + _ctx = ctx if ctx is not None else NULL_CTX + + if stop is None: + start, stop_ = 0, int(start_or_stop) + else: + start, stop_ = int(start_or_stop), int(stop) + step = int(step) + + assert step != 0, "irange: step darf nicht 0 sein" + + num = start + if step > 0: + while num < stop_: + yield Int(num, _ctx) + num += step + else: + while num > stop_: + yield Int(num, _ctx) + num += step diff --git a/utils/constants.py b/utils/constants.py deleted file mode 100644 index b68a192..0000000 --- a/utils/constants.py +++ /dev/null @@ -1,5 +0,0 @@ -from utils.literal import Literal - -MAX_VALUE = Literal(99999999999999999999) -MIN_VALUE = Literal(-99999999999999999999) - diff --git a/utils/literal.py b/utils/literal.py deleted file mode 100644 index c574306..0000000 --- a/utils/literal.py +++ /dev/null @@ -1,103 +0,0 @@ - -class Literal: - def __init__(self, value): - """Initialisiert Literal.""" - if isinstance(value, Literal): - self.value = value.value - else: - self.value = value - self.read_count = 0 - self.compare_count = 0 - - def reset_counters(self): - """Setzt alle Zähler auf 0 zurück.""" - self.read_count = 0 - self.compare_count = 0 - - def get(self): - """Liest den Wert aus.""" - self.read_count += 1 - return self.value - - def __eq__(self, other): - """Vergleicht den Wert mit einem anderen Wert.""" - if other is None: - return False - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value == other.value - - def __ne__(self, other): - """Vergleicht den Wert der Speicherzelle mit einem anderen Wert.""" - if other is None: - return True - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value != other.value - - def __lt__(self, other): - """Vergleicht den Wert der Speicherzelle mit einem anderen Wert.""" - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value < other.value - - def __le__(self, other): - """Vergleicht den Wert der Speicherzelle mit einem anderen Wert.""" - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value <= other.value - - def __gt__(self, other): - """Vergleicht den Wert der Speicherzelle mit einem anderen Wert.""" - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value > other.value - - def __ge__(self, other): - """Vergleicht den Wert der Speicherzelle mit einem anderen Wert.""" - assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell" - self.compare_count += 1 - self.read_count += 1 - other.read_count += 1 - return self.value >= other.value - - def __str__(self): - """Repräsentation des Werts.""" - return f"{self.value}" - - def __repr__(self): - """Repräsentation des Werts für Debugging-Zwecke.""" - return f"Literal(value={self.value}, reads={self.read_count})" - - def get_read_count(self): - """Gibt zurück, wie oft der Wert gelesen wurde.""" - return self.read_count - - def __int__(self): - """Gibt den Wert als Integer zurück.""" - self.read_count += 1 - return int(self.value) - - def succ(self): - return Literal(self.value+1) - - def pred(self): - return Literal(self.value-1) - - -if __name__ == "__main__": - l1 = Literal(5) - l2 = Literal(3) - print(l1 == l2) - print(l1 > l2) - diff --git a/utils/memory_array.py b/utils/memory_array.py deleted file mode 100644 index a8e033c..0000000 --- a/utils/memory_array.py +++ /dev/null @@ -1,126 +0,0 @@ -from utils.literal import Literal -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.project_dir import get_path -from random import randint - -class MemoryArray: - - def __init__(self, parm): - if isinstance(parm, Literal): - self.init_with_size(parm) - elif isinstance(parm, list): - self.size = len(parm) - self.cells = [MemoryCell(value) for value in parm] - else: - raise ValueError("Invalid parameter type") - - def init_with_size(self, size): - """Initialisiert ein Speicherarray mit einer bestimmten Größe.""" - assert isinstance(size, Literal), "Size must be a Literal or MemoryCell" - assert isinstance(size.value, int), "Size must be an int" - assert size.value > 0, "Size must be positive" - self.size = size.value - self.cells = [MemoryCell() for _ in range(self.size)] - - def __getitem__(self, index): - """Gibt den Wert einer Speicherzelle zurück.""" - assert isinstance(index, Literal), "Index must be a Literal or MemoryCell" - assert isinstance(index.value, int), "Index value must be an int" - assert 0 <= index.value < self.size, "Index out of bounds" - return self.cells[index.value] - - def __setitem__(self, index, value): - """Setzt den Wert einer Speicherzelle.""" - assert isinstance(index, Literal), "Index must be a Literal or MemoryCell" - assert isinstance(index.value, int), "Index value must be an int" - assert 0 <= index.value < self.size, "Index out of bounds" - assert isinstance(value, Literal), "Value must be a Literal or MemoryCell" - self.cells[index.value].set(value.value) - - def __len__(self): - """Gibt die Größe des Speicherarrays zurück.""" - return self.size - - def __str__(self): - """Gibt eine Liste der Speicherzellen zurück.""" - return str([cell.value for cell in self.cells]) - - def __iter__(self): - """Gibt einen Iterator über die Speicherzellen zurück.""" - return iter(self.cells) - - def indices(self): - """Gibt eine Liste der Indizes der Speicherzellen zurück.""" - return [Literal(i) for i in range(self.size)] - - def length(self): - """Gibt die Größe des Speicherarrays zurück.""" - return Literal(self.size) - - def count_compares(self): - return sum([cell.compare_count for cell in self.cells]) - - - def reset_counters(self): - """Setzt alle Zähler auf 0 zurück.""" - for cell in self.cells: - cell.reset_counters() - - @staticmethod - def create_random_array(count, min_value, max_value): - """Erzeugt ein zufälliges Speicherarray.""" - size = Literal(count) - a = MemoryArray(size) - for i in a.indices(): - a[i] = Literal(randint(min_value, max_value)) - a.reset_counters() - return a - - @staticmethod - def create_sorted_array(count): - """Erzeugt ein sortiertes Speicherarray.""" - a = MemoryArray(list(range(count))) - a.reset_counters() - return a - - - @staticmethod - def create_array_from_file(filename, limit=None): - """Erzeugt ein Speicherarray aus einer Datei.""" - filename = get_path(filename) - with open(filename) as f: - lines = f.readlines() - if limit is not None: - lines = lines[:limit] - size = Literal(len(lines)) - a = MemoryArray(size) - for i, line in enumerate(lines): - a[Literal(i)] = Literal(int(line)) - a.reset_counters() - return a - - def __str__(self): - result = "[ " - for cell in self.cells: - result += str(cell) + ", " - result += "]" - return result - - -if __name__ == "__main__": - import random - size = Literal(5) - a = MemoryArray(size) - for i in a.indices(): - a[i] = Literal(random.randint(1,100)) - print(a) - s = MemoryCell(0) - for cell in a.cells: - s += cell - print(s) - print(f"Anzahl der Additionen: {MemoryManager.count_adds()}") - - a = MemoryArray.create_array_from_file("data/seq0.txt") - print(a) - diff --git a/utils/memory_cell.py b/utils/memory_cell.py deleted file mode 100644 index ae748c3..0000000 --- a/utils/memory_cell.py +++ /dev/null @@ -1,194 +0,0 @@ -from utils.memory_manager import MemoryManager -from utils.literal import Literal - -class MemoryCell (Literal): - - def __new__(cls, *args, **kwargs): - """Erstellt eine neue Instanz von MemoryCell.""" - instance = MemoryManager().acquire_cell() - if instance is None: - instance = super().__new__(cls) - MemoryManager().register_cell(instance) - return instance - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - MemoryManager().release_cell(self) - - def __init__(self, value=None): - """Initialisiert eine Speicherzelle mit optionalem Startwert.""" - super().__init__(value) - self.write_count = 0 - self.add_count = 0 - self.sub_count = 0 - self.mul_count = 0 - self.div_count = 0 - self.bitop_count = 0 - if value is not None: - self.write_count +=1 - else: - self.value = 0 - - def reset_counters(self): - """Setzt alle Zähler auf 0 zurück.""" - super().reset_counters() - self.write_count = 0 - self.add_count = 0 - self.sub_count = 0 - self.mul_count = 0 - self.div_count = 0 - self.bitop_count = 0 - - def set(self, new_value): - """Schreibt einen neuen Wert in die Speicherzelle und erhöht den Schreibzähler.""" - self.write_count += 1 - if isinstance(new_value, Literal): - self.value = new_value.value - else: - self.value = new_value - - def add(self, other): - """Addiert den Wert der Speicherzelle mit einem anderen Wert.""" - self.set((self + other).value) - - def sub(self, other): - """Subtrahiert den Wert der Speicherzelle mit einem anderen Wert.""" - self.set((self - other).value) - - def mul(self, other): - """Multipliziert den Wert der Speicherzelle mit einem anderen Wert.""" - self.set((self * other).value) - - def div(self, other): - """Dividiert den Wert der Speicherzelle durch einen anderen Wert.""" - self.set((self // other).value) - - def modulo(self, other): - """Berechnet den Modulo des Wertes der Speicherzelle durch einen anderen Wert.""" - self.set((self % other).value) - - def lshift(self, other): - """Verschiebt den Wert der Speicherzelle um eine bestimmte Anzahl von Bits nach links.""" - assert isinstance(other, Literal), "Can only lshift Literal or MemoryCell by MemoryCell" - self.bitop_count += 1 - self.read_count += 1 - self.write_count += 1 - other.read_count += 1 - self.value <<= other.value - - def rshift(self, other): - """Verschiebt den Wert der Speicherzelle um eine bestimmte Anzahl von Bits nach rechts.""" - assert isinstance(other, Literal), "Can only rshift Literal or MemoryCell by MemoryCell" - self.bitop_count += 1 - self.read_count += 1 - self.write_count += 1 - other.read_count += 1 - self.value >>= other.value - - def and_op(self, other): - """Führt ein Bitweise AND auf den Wert der Speicherzelle mit einem anderen Wert aus.""" - assert isinstance(other, Literal), "Can only and Literal or MemoryCell with MemoryCell" - self.bitop_count += 1 - self.read_count += 1 - self.write_count += 1 - other.read_count += 1 - self.value &= other.value - - def or_op(self, other): - """Führt ein Bitweise OR auf den Wert der Speicherzelle mit einem anderen Wert aus.""" - assert isinstance(other, Literal), "Can only or Literal or MemoryCell with MemoryCell" - self.bitop_count += 1 - self.read_count += 1 - self.write_count += 1 - other.read_count += 1 - self.value |= other.value - - def xor_op(self, other): - """Führt ein Bitweise XOR auf den Wert der Speicherzelle mit einem anderen Wert aus.""" - assert isinstance(other, Literal), "Can only xor Literal or MemoryCell with MemoryCell" - self.bitop_count += 1 - self.read_count += 1 - self.write_count += 1 - other.read_count += 1 - self.value ^= other.value - - def get_write_count(self): - """Gibt zurück, wie oft der Wert geschrieben wurde.""" - return self.write_count - - def __repr__(self): - """Repräsentation der Speicherzelle für Debugging-Zwecke.""" - return f"MemoryCell(value={self.value}, reads={self.read_count}, writes={self.write_count})" - - def __add__(self, other): - assert isinstance(other, Literal), "Can only add Literal or MemoryCell to MemoryCell" - self.add_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value + other.value) - - def __sub__(self, other): - assert isinstance(other, Literal), "Can only add Literal or MemoryCell to MemoryCell" - self.sub_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value - other.value) - - def __mul__(self, other): - assert isinstance(other, Literal), "Can only mul Literal or MemoryCell with MemoryCell" - self.mul_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value * other.value) - - def __truediv__(self, other): - assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell" - self.div_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value / other.value) - - def __floordiv__(self, other): - assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell" - self.div_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value // other.value) - - def __mod__(self, other): - assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell" - self.div_count += 1 - self.read_count += 1 - other.read_count += 1 - return Literal(self.value % other.value) - - def __iadd__(self, other): - self.add(other) - return self - - def __isub__(self, other): - self.sub(other) - return self - - def __imul__(self, other): - self.mul(other) - return self - - def __itruediv__(self, other): - self.set(self // other) - return self - - def __ifloordiv__(self, other): - self.div(other) - return self - - -if __name__ == "__main__": - a = MemoryCell(5) - b = MemoryCell(3) - a += b - print(f"Ergebnis: {a}") - print(f"a wurde {a.get_read_count()} mal gelesen und {a.get_write_count()} mal geschrieben.") - print(f"b wurde {b.get_read_count()} mal gelesen und {b.get_write_count()} mal geschrieben.") diff --git a/utils/memory_manager.py b/utils/memory_manager.py deleted file mode 100644 index f1a5ba2..0000000 --- a/utils/memory_manager.py +++ /dev/null @@ -1,148 +0,0 @@ -import matplotlib.pyplot as plt -import queue - - -class MemoryManager: - - _instance = None - stats = {} - - def __new__(cls, *args, **kwargs): - """Erstellt eine einzige Instanz von MemoryManager.""" - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._initialize() # Eigene Init-Methode, damit __init__ nicht mehrfach läuft - return cls._instance - - def _initialize(self): - """Initialisiert die Speicherverwaltung (einmalig).""" - self.cells = [] - self._pool = queue.Queue() - self._finalizers = {} - - - @staticmethod - def count_cells(): - return len(MemoryManager().cells) - - - @staticmethod - def count_reads(): - return sum([cell.read_count for cell in MemoryManager().cells]) - - @staticmethod - def count_writes(): - return sum([cell.write_count for cell in MemoryManager().cells]) - - @staticmethod - def count_compares(): - return sum([cell.compare_count for cell in MemoryManager().cells]) - - @staticmethod - def count_adds(): - return sum([cell.add_count for cell in MemoryManager().cells]) - - @staticmethod - def count_subs(): - return sum([cell.sub_count for cell in MemoryManager().cells]) - - @staticmethod - def count_muls(): - return sum([cell.mul_count for cell in MemoryManager().cells]) - - @staticmethod - def count_divs(): - return sum([cell.div_count for cell in MemoryManager().cells]) - - @staticmethod - def count_bitops(): - return sum([cell.bitop_count for cell in MemoryManager().cells]) - - @staticmethod - def reset(): - manager = MemoryManager() - for cell in manager.cells: - cell.reset_counters() - - @staticmethod - def purge(): - MemoryManager._instance = None - - @staticmethod - def save_stats(count): - data = { "cells": MemoryManager.count_cells(), - "reads": MemoryManager.count_reads(), - "writes": MemoryManager.count_writes(), - "compares": MemoryManager.count_compares(), - "adds": MemoryManager.count_adds(), - "subs": MemoryManager.count_subs(), - "muls": MemoryManager.count_muls(), - "divs": MemoryManager.count_divs(), - "bitops": MemoryManager.count_bitops() } - MemoryManager.stats[count] = data - - @staticmethod - def plot_stats(labels): - data = MemoryManager.stats - x = list(data.keys()) - - fig, axes = plt.subplots(len(labels), 1, figsize=(8, 4 * len(labels)), sharex=True) - - if len(labels) == 1: - axes = [axes] # Falls nur ein Plot vorhanden ist, in eine Liste umwandeln - - for ax, l in zip(axes, labels): - y = [data[k][l] for k in x] - ax.plot(x, y, label=l) - ax.set_ylabel(l) - ax.legend() - - plt.xlabel("n") - plt.show() - - - def acquire_cell(self): - try: - return self._pool.get_nowait() - except queue.Empty: - return None - - def register_cell(self, cell): - self.cells.append(cell) - - def release_cell(self, cell): - self._pool.put(cell) - - -class Testcell: - - def __new__(cls, *args, **kwargs): - instance = MemoryManager().acquire_cell() - if instance is None: - instance = super().__new__(cls) - MemoryManager().register_cell(instance) - return instance - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - MemoryManager().release_cell(self) - - -if __name__ == "__main__": - - # Einfaches Anlegen einer Zelle - a = Testcell() - print(MemoryManager.count_cells()) - - # Anlegen einer Zelle und Beenden des Scopes - with Testcell() as b: - print(MemoryManager.count_cells()) - - print(MemoryManager.count_cells()) - - # Reuse einer Zelle - c = Testcell() - print(MemoryManager.count_cells()) - diff --git a/utils/memory_range.py b/utils/memory_range.py deleted file mode 100644 index 6354159..0000000 --- a/utils/memory_range.py +++ /dev/null @@ -1,29 +0,0 @@ -from utils.literal import Literal - -# a generator that yields items instead of returning a list -def mrange(parm1, parm2=None, parm3=None): - if parm2 is None: - start = 0 - stop = int(parm1) - step = 1 - elif parm3 is None: - start = int(parm1) - stop = int(parm2) - step = 1 - else: - start = int(parm1) - stop = int(parm2) - step = int(parm3) - num = start - if step > 0: - while num < stop: - yield Literal(num) - num += step - else: - while num > stop: - yield Literal(num) - num += step - -if __name__ == "__main__": - for l in mrange(10): - print(l) diff --git a/utils/test_algo_array.py b/utils/test_algo_array.py new file mode 100644 index 0000000..d530962 --- /dev/null +++ b/utils/test_algo_array.py @@ -0,0 +1,175 @@ +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() diff --git a/utils/test_algo_int.py b/utils/test_algo_int.py new file mode 100644 index 0000000..cd714c9 --- /dev/null +++ b/utils/test_algo_int.py @@ -0,0 +1,193 @@ +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() diff --git a/utils/test_memory_array.py b/utils/test_memory_array.py deleted file mode 100644 index 5b5c81d..0000000 --- a/utils/test_memory_array.py +++ /dev/null @@ -1,47 +0,0 @@ -from unittest import TestCase -from utils.literal import Literal -from utils.memory_array import MemoryArray -import random - -class TestMemoryArray(TestCase): - - def test_create_array(self): - l = random.randint(5,10) - size = Literal(l) - a = MemoryArray(size) - self.assertEqual(len(a), l) - - def test_set_item(self): - l = random.randint(5,10) - size = Literal(l) - a = MemoryArray(size) - i = Literal(random.randint(0,l-1)) - v = Literal(random.randint(1,100)) - a[i] = v - self.assertEqual(a[i].value, v.value) - - def test_get_item(self): - l = random.randint(5,10) - values = [random.randint(1,100) for _ in range(l)] - a = MemoryArray(values) - for pos, i in enumerate(a.indices()): - self.assertEqual(a[i].value, values[pos]) - - def test_reset_counters(self): - l = random.randint(5,10) - values = [random.randint(1,100) for _ in range(l)] - a = MemoryArray(values) - for i in a.indices(): - self.assertEqual(a[i].write_count, 1) - a.reset_counters() - for i in a.indices(): - self.assertEqual(a[i].write_count, 0) - - def test_create_random_array(self): - a = MemoryArray.create_random_array(10, 1, 100) - self.assertEqual(len(a), 10) - - def test_create_array_from_file(self): - a = MemoryArray.create_array_from_file("data/seq0.txt") - self.assertEqual(len(a), 14) - diff --git a/utils/test_memory_cell.py b/utils/test_memory_cell.py deleted file mode 100644 index 8b6578a..0000000 --- a/utils/test_memory_cell.py +++ /dev/null @@ -1,166 +0,0 @@ -from unittest import TestCase -from utils.memory_cell import MemoryCell -from utils.literal import Literal -import random - - -class TestMemoryCell(TestCase): - - def test_create_cell(self): - v = random.randint(1, 100) - cell = MemoryCell(v) - self.assertEqual(cell.value, v) - self.assertEqual(cell.read_count, 0) - self.assertEqual(cell.write_count, 1) - self.assertEqual(cell.add_count, 0) - self.assertEqual(cell.sub_count, 0) - self.assertEqual(cell.mul_count, 0) - self.assertEqual(cell.div_count, 0) - self.assertEqual(cell.bitop_count, 0) - - def test_cast_cell(self): - v = random.randint(1, 100) - cell = MemoryCell(v) - self.assertEqual(int(cell), v) - self.assertEqual(cell.read_count, 1) - self.assertEqual(cell.write_count, 1) - self.assertEqual(cell.add_count, 0) - self.assertEqual(cell.sub_count, 0) - self.assertEqual(cell.mul_count, 0) - self.assertEqual(cell.div_count, 0) - self.assertEqual(cell.bitop_count, 0) - - def test_add(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - - # "in place" Addition zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - cell1 += cell2 - self.assertEqual(cell1.value, v1 + v2) - self.assertEqual(cell1.add_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(cell1, MemoryCell)) - self.assertTrue(isinstance(cell2, MemoryCell)) - - # Freie Addition zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - result = cell1 + cell2 - - self.assertEqual(result.value, v1 + v2) - self.assertEqual(cell1.add_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(result, Literal)) - - def test_sub(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - - # "in place" Subtraktion zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - cell1 -= cell2 - - self.assertEqual(cell1.value, v1 - v2) - self.assertEqual(cell1.sub_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(cell1, MemoryCell)) - self.assertTrue(isinstance(cell2, MemoryCell)) - - # Freie Subtraktion zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - result = cell1 - cell2 - self.assertEqual(result.value, v1 - v2) - self.assertEqual(cell1.sub_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(result, Literal)) - - def test_mul(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - - # "in place" Multiplikation zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - cell1 *= cell2 - - self.assertEqual(cell1.value, v1 * v2) - self.assertEqual(cell1.mul_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(cell1, MemoryCell)) - self.assertTrue(isinstance(cell2, MemoryCell)) - - # Freie Multiplikation zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - result = cell1 * cell2 - self.assertEqual(result.value, v1 * v2) - self.assertEqual(cell1.mul_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(result, Literal)) - - def test_div(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - - # "in place" Division zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - cell1 //= cell2 - - self.assertEqual(cell1.value, v1 // v2) - self.assertEqual(cell1.div_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(cell1, MemoryCell)) - self.assertTrue(isinstance(cell2, MemoryCell)) - - # Freie Division zweier MemoryCells - cell1 = MemoryCell(v1) - cell2 = MemoryCell(v2) - result = cell1 // cell2 - self.assertEqual(result.value, v1 // v2) - self.assertEqual(cell1.div_count, 1) - self.assertEqual(cell1.read_count, 1) - self.assertEqual(cell2.read_count, 1) - self.assertTrue(isinstance(result, Literal)) - - def test_reset_counters(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - cell = MemoryCell(v1) - cell += Literal(v2) - - self.assertEqual(cell.value, v1+v2) - self.assertEqual(cell.read_count, 1) - self.assertEqual(cell.add_count, 1) - self.assertEqual(cell.write_count, 2) - - cell.reset_counters() - - self.assertEqual(cell.value, v1+v2) - self.assertEqual(cell.read_count, 0) - self.assertEqual(cell.add_count, 0) - self.assertEqual(cell.write_count, 0) - - - def test_set(self): - v1 = random.randint(1, 100) - v2 = random.randint(1, 100) - - cell = MemoryCell(v1) - cell.set(v2) - - self.assertEqual(cell.value, v2) - self.assertEqual(cell.read_count, 0) - self.assertEqual(cell.write_count, 2) - diff --git a/vorlesung/L01_grundlagen/euklid.py b/vorlesung/L01_grundlagen/euklid.py index ae79752..77634a2 100644 --- a/vorlesung/L01_grundlagen/euklid.py +++ b/vorlesung/L01_grundlagen/euklid.py @@ -1,13 +1,13 @@ -from utils.memory_cell import MemoryCell -from utils.literal import Literal +from utils import AlgoContext, Int -x = MemoryCell(int(input("Erste Zahl: "))) -y = MemoryCell(int(input("Zweite Zahl: "))) +ctx = AlgoContext() +x = Int(int(input("Erste Zahl: ")), ctx) +y = Int(int(input("Zweite Zahl: ")), ctx) -while x > Literal(0): +while x > 0: if x < y: x, y = y, x x -= y print(y) -print(f"Insgesamt gab es {x.sub_count + y.sub_count} Subtraktionen.") \ No newline at end of file +print(f"Insgesamt gab es {ctx.subtractions} Subtraktionen.") diff --git a/vorlesung/L02_elementares_sortieren/bubble_game.py b/vorlesung/L02_elementares_sortieren/bubble_game.py index 11bda67..8a00c05 100644 --- a/vorlesung/L02_elementares_sortieren/bubble_game.py +++ b/vorlesung/L02_elementares_sortieren/bubble_game.py @@ -1,22 +1,25 @@ import random import pygame from utils.game import Game -from utils.memory_array import MemoryArray +from utils.algo_context import AlgoContext +from utils.algo_array import Array from bubble_sorting import bubble_sort_stepwise WHITE = (255, 255, 255) -BLUE = (0, 0, 255) +BLUE = (0, 0, 255) + class BubbleGame(Game): def __init__(self): super().__init__("Bubble Game", fps=60, size=(400, 400)) random.seed() - l =list(range(1, 101)) + l = list(range(1, 101)) random.shuffle(l) - self.z = MemoryArray(l) + self.ctx = AlgoContext() + self.z = Array(l, self.ctx) self.finished = False - self.sort_generator = bubble_sort_stepwise(self.z) + self.sort_generator = bubble_sort_stepwise(self.z, self.ctx) def update_game(self): if not self.finished: @@ -29,7 +32,7 @@ class BubbleGame(Game): def draw_game(self): self.screen.fill(WHITE) for i, cell in enumerate(self.z): - x = 50 + i*3 + x = 50 + i * 3 y = 350 - cell.value * 3 pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3)) super().draw_game() @@ -38,4 +41,3 @@ class BubbleGame(Game): if __name__ == "__main__": b = BubbleGame() b.run() - diff --git a/vorlesung/L02_elementares_sortieren/bubble_sorting.py b/vorlesung/L02_elementares_sortieren/bubble_sorting.py index 1ee4d8e..71f02b3 100644 --- a/vorlesung/L02_elementares_sortieren/bubble_sorting.py +++ b/vorlesung/L02_elementares_sortieren/bubble_sorting.py @@ -1,83 +1,82 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_range import irange -def bubble_sort_stepwise(z: MemoryArray): - n = z.length() - for i in mrange(n.pred()): - for j in mrange(n.pred(), i, -1): - if z[j.pred()] > z[j]: - swap(z, j, j.pred()) + +def bubble_sort_stepwise(z: Array, ctx: AlgoContext): + """ + Bubble Sort – schrittweise Variante (Generator). + + Gibt nach jedem Tausch den aktuellen Array-Zustand zurück. + Wird von bubble_game.py für die Visualisierung verwendet. + """ + n = len(z) + for i in irange(n - 1): + for j in irange(n - 1, i, -1): + if z[j - 1] > z[j]: + z.swap(j - 1, j) yield z -def bubble_sort2_stepwise(z: MemoryArray): - n = MemoryCell(z.length()) - true = Literal(1) - false = Literal(0) - sortiert = MemoryCell() +def bubble_sort2_stepwise(z: Array, ctx: AlgoContext): + """ + Optimierter Bubble Sort mit Frühausstieg – schrittweise Variante. + + Bricht ab, wenn in einem Durchlauf kein Tausch stattgefunden hat. + """ + n = len(z) while True: - sortiert.set(true) - for i in mrange(n.pred()): - if z[i] > z[i.succ()]: - swap(z, i, i.succ()) - sortiert.set(false) + swapped = False + for i in irange(n - 1): + if z[i] > z[i + 1]: + z.swap(i, i + 1) + swapped = True yield z - n -= Literal(1) - if sortiert == true or n <= Literal(1): + n -= 1 + if not swapped or n <= 1: break -def bubble_sort(z: MemoryArray): - sort_generator = bubble_sort_stepwise(z) - while True: - try: - next(sort_generator) - except StopIteration: - break +def bubble_sort(z: Array, ctx: AlgoContext): + """Bubble Sort – vollständige Ausführung ohne Visualisierung.""" + for _ in bubble_sort_stepwise(z, ctx): + pass -def bubble_sort2(z: MemoryArray): - sort_generator = bubble_sort2_stepwise(z) - while True: - try: - next(sort_generator) - except StopIteration: - break +def bubble_sort2(z: Array, ctx: AlgoContext): + """Optimierter Bubble Sort – vollständige Ausführung ohne Visualisierung.""" + for _ in bubble_sort2_stepwise(z, ctx): + pass -def sort_file(filename, sort_func): - z = MemoryArray.create_array_from_file(filename) - sort_func(z) - return z - def analyze_complexity(sort_func, sizes, presorted=False): """ - Analysiert die Komplexität einer Sortierfunktion. + Analysiert die Komplexität einer Sortierfunktion über mehrere Eingabegrößen. - :param sort_func: Die Funktion, die analysiert wird. - :param sizes: Eine Liste von Eingabegrößen für die Analyse. + Parameters + ---------- + sort_func : callable + Signatour: sort_func(z: Array, ctx: AlgoContext) + sizes : list[int] + Eingabegrößen für die Analyse. + presorted : bool + True → sortiertes Eingabe-Array (Best-Case-Analyse). """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, -100, 100) - sort_func(random_array) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) + ctx.plot_stats(["comparisons", "writes"]) -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) if __name__ == '__main__': - analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) -# analyze_complexity(bubble_sort2, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) -# analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) -# analyze_complexity(bubble_sort2, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) + analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +# analyze_complexity(bubble_sort2, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +# analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) +# analyze_complexity(bubble_sort2, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) diff --git a/vorlesung/L02_elementares_sortieren/insert_game.py b/vorlesung/L02_elementares_sortieren/insert_game.py index 3d17b29..6d24b3c 100644 --- a/vorlesung/L02_elementares_sortieren/insert_game.py +++ b/vorlesung/L02_elementares_sortieren/insert_game.py @@ -1,22 +1,25 @@ import random import pygame from utils.game import Game -from utils.memory_array import MemoryArray +from utils.algo_context import AlgoContext +from utils.algo_array import Array from insert_sorting import insert_sort_stepwise WHITE = (255, 255, 255) -BLUE = (0, 0, 255) +BLUE = (0, 0, 255) + class InsertGame(Game): def __init__(self): super().__init__("Insert Game", fps=60, size=(400, 400)) random.seed() - l =list(range(1, 101)) + l = list(range(1, 101)) random.shuffle(l) - self.z = MemoryArray(l) + self.ctx = AlgoContext() + self.z = Array(l, self.ctx) self.finished = False - self.sort_generator = insert_sort_stepwise(self.z) + self.sort_generator = insert_sort_stepwise(self.z, self.ctx) def update_game(self): if not self.finished: @@ -29,7 +32,7 @@ class InsertGame(Game): def draw_game(self): self.screen.fill(WHITE) for i, cell in enumerate(self.z): - x = 50 + i*3 + x = 50 + i * 3 y = 350 - cell.value * 3 pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3)) super().draw_game() @@ -38,4 +41,3 @@ class InsertGame(Game): if __name__ == "__main__": b = InsertGame() b.run() - diff --git a/vorlesung/L02_elementares_sortieren/insert_sorting.py b/vorlesung/L02_elementares_sortieren/insert_sorting.py index e962dc3..1aa7fb8 100644 --- a/vorlesung/L02_elementares_sortieren/insert_sorting.py +++ b/vorlesung/L02_elementares_sortieren/insert_sorting.py @@ -1,61 +1,49 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int +from utils.algo_range import irange -def insert_sort_stepwise(z: MemoryArray): - n = z.length() - j = MemoryCell() - elem = MemoryCell() - for i in mrange(n): - elem.set(z[i]) - j.set(i) - while j > Literal(0) and z[j.pred()] > elem: - z[j].set(z[j.pred()]) - j -= Literal(1) + +def insert_sort_stepwise(z: Array, ctx: AlgoContext): + """ + Insertion Sort – schrittweise Variante (Generator). + + Gibt nach jedem Einfügevorgang den aktuellen Array-Zustand zurück. + """ + n = len(z) + elem = Int(0, ctx) # Zwischenregister für das einzufügende Element + + for i in irange(n): + elem.set(z[i]) # 1 read + 1 write + j = Int(int(i), ctx) + while j > 0 and z[j - 1] > elem: + z[j] = z[j - 1] # 1 read + 1 write + j -= 1 yield z - z[j].set(elem) + z[j] = elem # 1 read + 1 write yield z -def insert_sort(z: MemoryArray): - sort_generator = insert_sort_stepwise(z) - while True: - try: - next(sort_generator) - except StopIteration: - break - -def sort_file(filename, sort_func): - z = MemoryArray.create_array_from_file(filename) - sort_func(z) - return z +def insert_sort(z: Array, ctx: AlgoContext): + """Insertion Sort – vollständige Ausführung ohne Visualisierung.""" + for _ in insert_sort_stepwise(z, ctx): + pass def analyze_complexity(sort_func, sizes, presorted=False): - """ - Analysiert die Komplexität einer Sortierfunktion. - - :param sort_func: Die Funktion, die analysiert wird. - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, -100, 100) - sort_func(random_array) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) + ctx.plot_stats(["comparisons", "writes"]) -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) if __name__ == '__main__': - analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) - #analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) + analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +# analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) diff --git a/vorlesung/L02_elementares_sortieren/select_game.py b/vorlesung/L02_elementares_sortieren/select_game.py index 8c65eb3..5df07f7 100644 --- a/vorlesung/L02_elementares_sortieren/select_game.py +++ b/vorlesung/L02_elementares_sortieren/select_game.py @@ -1,22 +1,25 @@ import random import pygame from utils.game import Game -from utils.memory_array import MemoryArray +from utils.algo_context import AlgoContext +from utils.algo_array import Array from select_sorting import select_sort_stepwise WHITE = (255, 255, 255) -BLUE = (0, 0, 255) +BLUE = (0, 0, 255) + class SelectGame(Game): def __init__(self): super().__init__("Select Game", fps=60, size=(400, 400)) random.seed() - l =list(range(1, 101)) + l = list(range(1, 101)) random.shuffle(l) - self.z = MemoryArray(l) + self.ctx = AlgoContext() + self.z = Array(l, self.ctx) self.finished = False - self.sort_generator = select_sort_stepwise(self.z) + self.sort_generator = select_sort_stepwise(self.z, self.ctx) def update_game(self): if not self.finished: @@ -29,7 +32,7 @@ class SelectGame(Game): def draw_game(self): self.screen.fill(WHITE) for i, cell in enumerate(self.z): - x = 50 + i*3 + x = 50 + i * 3 y = 350 - cell.value * 3 pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3)) super().draw_game() @@ -38,4 +41,3 @@ class SelectGame(Game): if __name__ == "__main__": b = SelectGame() b.run() - diff --git a/vorlesung/L02_elementares_sortieren/select_sorting.py b/vorlesung/L02_elementares_sortieren/select_sorting.py index 6f80f19..ac500e4 100644 --- a/vorlesung/L02_elementares_sortieren/select_sorting.py +++ b/vorlesung/L02_elementares_sortieren/select_sorting.py @@ -1,58 +1,47 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int +from utils.algo_range import irange -def select_sort_stepwise(z: MemoryArray): - n = z.length() - cur_min = MemoryCell() - for i in mrange(n): - cur_min.set(i) - for j in mrange(i.succ(), n): + +def select_sort_stepwise(z: Array, ctx: AlgoContext): + """ + Selection Sort – schrittweise Variante (Generator). + + Gibt nach jedem Platztausch den aktuellen Array-Zustand zurück. + """ + n = len(z) + cur_min = Int(0, ctx) # Index des aktuellen Minimums + + for i in irange(n): + cur_min.set(Int(int(i), ctx)) + for j in irange(int(i) + 1, n): if z[j] < z[cur_min]: - cur_min.set(j) - swap(z, i, int(cur_min)) + cur_min.set(Int(int(j), ctx)) + z.swap(int(i), int(cur_min)) yield z -def select_sort(z: MemoryArray): - sort_generator = select_sort_stepwise(z) - while True: - try: - next(sort_generator) - except StopIteration: - break - -def sort_file(filename, sort_func): - z = MemoryArray.create_array_from_file(filename) - sort_func(z) - return z +def select_sort(z: Array, ctx: AlgoContext): + """Selection Sort – vollständige Ausführung ohne Visualisierung.""" + for _ in select_sort_stepwise(z, ctx): + pass def analyze_complexity(sort_func, sizes, presorted=False): - """ - Analysiert die Komplexität einer Sortierfunktion. - - :param sort_func: Die Funktion, die analysiert wird. - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, -100, 100) - sort_func(random_array) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) + ctx.plot_stats(["comparisons", "writes"]) -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) if __name__ == '__main__': - analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) -# analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) + analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +# analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True) diff --git a/vorlesung/L03_fortgeschrittenes_sortieren/heap_game.py b/vorlesung/L03_fortgeschrittenes_sortieren/heap_game.py index 8523e39..4546399 100644 --- a/vorlesung/L03_fortgeschrittenes_sortieren/heap_game.py +++ b/vorlesung/L03_fortgeschrittenes_sortieren/heap_game.py @@ -1,23 +1,25 @@ import random import pygame from utils.game import Game -from utils.memory_array import MemoryArray -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array from heap_sorting import heap_sort_stepwise WHITE = (255, 255, 255) -BLUE = (0, 0, 255) +BLUE = (0, 0, 255) + class HeapGame(Game): def __init__(self): super().__init__("Heap Game", fps=20, size=(400, 400)) random.seed() - l =list(range(1, 101)) + l = list(range(1, 101)) random.shuffle(l) - self.z = MemoryArray(l) + self.ctx = AlgoContext() + self.z = Array(l, self.ctx) self.finished = False - self.sort_generator = heap_sort_stepwise(self.z) + self.sort_generator = heap_sort_stepwise(self.z, self.ctx) def update_game(self): if not self.finished: @@ -30,7 +32,7 @@ class HeapGame(Game): def draw_game(self): self.screen.fill(WHITE) for i, cell in enumerate(self.z): - x = 50 + i*3 + x = 50 + i * 3 y = 350 - cell.value * 3 pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3)) super().draw_game() @@ -39,4 +41,3 @@ class HeapGame(Game): if __name__ == "__main__": sort_game = HeapGame() sort_game.run() - diff --git a/vorlesung/L03_fortgeschrittenes_sortieren/heap_sorting.py b/vorlesung/L03_fortgeschrittenes_sortieren/heap_sorting.py index 3400789..c3a5400 100644 --- a/vorlesung/L03_fortgeschrittenes_sortieren/heap_sorting.py +++ b/vorlesung/L03_fortgeschrittenes_sortieren/heap_sorting.py @@ -1,93 +1,85 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal - -def heap_sort_stepwise(z: MemoryArray): - n = z.length() - yield from make_max_heap(z) - with MemoryCell(n) as heapsize: - for i in mrange(n, 1, -1): - swap(z, 0, i.pred()) - yield z - heapsize.set(heapsize.pred()) - yield from max_heapyfy(z, Literal(1), heapsize) +from utils.algo_context import AlgoContext +from utils.algo_array import Array -def heap_sort(z: MemoryArray): - sort_generator = heap_sort_stepwise(z) - while True: - try: - next(sort_generator) - except StopIteration: - break +# Heap verwendet 1-basierte Indizierung intern; +# adjust_index() rechnet auf 0-basierte Array-Positionen um. + +def left_child(i: int) -> int: + return 2 * i + +def right_child(i: int) -> int: + return 2 * i + 1 + +def adjust_index(i: int) -> int: + """Konvertiert 1-basierten Heap-Index in 0-basierten Array-Index.""" + return i - 1 -def left_child(i: Literal): - return Literal(2 * int(i)) +def heap_sort_stepwise(z: Array, ctx: AlgoContext): + """ + Heapsort – schrittweise Variante (Generator). + + Baut zunächst einen Max-Heap auf, dann sortiert er durch Tauschen. + """ + n = len(z) + yield from make_max_heap(z, n, ctx) + heapsize = n + for i in range(n, 1, -1): + z.swap(0, i - 1) + yield z + heapsize -= 1 + yield from max_heapify(z, 1, heapsize, ctx) -def right_child(i: Literal): - return Literal(2 * int(i) + 1) +def heap_sort(z: Array, ctx: AlgoContext): + """Heapsort – vollständige Ausführung ohne Visualisierung.""" + for _ in heap_sort_stepwise(z, ctx): + pass -def adjust_index(i: Literal): - return i.pred() +def make_max_heap(z: Array, n: int, ctx: AlgoContext): + """Baut einen Max-Heap in-place auf.""" + for i in range(n // 2, 0, -1): + yield from max_heapify(z, i, n, ctx) -def make_max_heap(z: MemoryArray): - n = z.length() - for i in mrange(int(n) // 2, 0, -1): - yield from max_heapyfy(z, i, n) +def max_heapify(z: Array, i: int, heapsize: int, ctx: AlgoContext): + """ + Stellt die Max-Heap-Eigenschaft für den Teilbaum bei Index i wieder her. - -def max_heapyfy(z: MemoryArray, i: Literal, heapsize: Literal): + i und heapsize sind plain int (1-basiert). Vergleiche auf Array-Inhalten + werden über Int automatisch gezählt. + """ l = left_child(i) r = right_child(i) - with MemoryCell(i) as max_value: - if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]: - max_value.set(l) - if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_value)]: - max_value.set(r) - if max_value != i: - swap(z, int(i)-1, int(max_value)-1) - yield z - yield from max_heapyfy(z, max_value, heapsize) + max_val = i + if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]: + max_val = l + if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_val)]: + max_val = r -def sort_file(filename, sort_func): - z = MemoryArray.create_array_from_file(filename) - sort_func(z) - return z + if max_val != i: + z.swap(adjust_index(i), adjust_index(max_val)) + yield z + yield from max_heapify(z, max_val, heapsize, ctx) def analyze_complexity(sort_func, sizes, presorted=False): - """ - Analysiert die Komplexität einer Sortierfunktion. - - :param sort_func: Die Funktion, die analysiert wird. - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, -100, 100) - sort_func(random_array) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) - - -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) + ctx.plot_stats(["comparisons", "writes"]) if __name__ == '__main__': sizes = range(10, 101, 10) analyze_complexity(heap_sort, sizes) - # analyze_complexity(quick_sort, sizes, True) diff --git a/vorlesung/L03_fortgeschrittenes_sortieren/quick_game.py b/vorlesung/L03_fortgeschrittenes_sortieren/quick_game.py index dd2f630..e9133a2 100644 --- a/vorlesung/L03_fortgeschrittenes_sortieren/quick_game.py +++ b/vorlesung/L03_fortgeschrittenes_sortieren/quick_game.py @@ -1,23 +1,25 @@ import random import pygame from utils.game import Game -from utils.memory_array import MemoryArray -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array from quick_sorting import quick_sort_stepwise WHITE = (255, 255, 255) -BLUE = (0, 0, 255) +BLUE = (0, 0, 255) + class QuickGame(Game): def __init__(self): super().__init__("Quick Game", fps=10, size=(400, 400)) random.seed() - l =list(range(1, 101)) + l = list(range(1, 101)) random.shuffle(l) - self.z = MemoryArray(l) + self.ctx = AlgoContext() + self.z = Array(l, self.ctx) self.finished = False - self.sort_generator = quick_sort_stepwise(self.z, Literal(0), Literal(self.z.length().pred())) + self.sort_generator = quick_sort_stepwise(self.z, self.ctx) def update_game(self): if not self.finished: @@ -30,7 +32,7 @@ class QuickGame(Game): def draw_game(self): self.screen.fill(WHITE) for i, cell in enumerate(self.z): - x = 50 + i*3 + x = 50 + i * 3 y = 350 - cell.value * 3 pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3)) super().draw_game() @@ -39,4 +41,3 @@ class QuickGame(Game): if __name__ == "__main__": sort_game = QuickGame() sort_game.run() - diff --git a/vorlesung/L03_fortgeschrittenes_sortieren/quick_sorting.py b/vorlesung/L03_fortgeschrittenes_sortieren/quick_sorting.py index 9857f76..b2c8066 100644 --- a/vorlesung/L03_fortgeschrittenes_sortieren/quick_sorting.py +++ b/vorlesung/L03_fortgeschrittenes_sortieren/quick_sorting.py @@ -1,81 +1,79 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal - -def quick_sort_stepwise(z: MemoryArray, l: Literal, r: Literal): - if l < r: - q = partition(z, l, r) - yield z - yield from quick_sort_stepwise(z, l, q.pred()) - yield from quick_sort_stepwise(z, q.succ(), r) - yield z +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int -def partition(z: MemoryArray, l: Literal, r: Literal): - with MemoryCell(z[r]) as pivot, MemoryCell(l) as i, MemoryCell(r.pred()) as j: - while i < j: - while z[i] < pivot: - i.set(i.succ()) - while j > l and z[j] >= pivot: - j.set(j.pred()) - if i < j: - swap(z, int(i), int(j)) - i.set(i.succ()) - j.set(j.pred()) - if i == j and z[i] < pivot: - i.set(i.succ()) - if z[i] != pivot: - swap(z, int(i), int(r)) - return Literal(i) +def quick_sort_stepwise(z: Array, ctx: AlgoContext, l: int = 0, r: int = None): + """ + Quicksort – schrittweise Variante (Generator). - -def quick_sort(z: MemoryArray, l: Literal = None, r: Literal = None): - if l is None: - l = Literal(0) + l, r sind 0-basierte Grenzen (plain int). Alle Vergleiche auf Array-Inhalten + werden über Int automatisch gezählt. + """ if r is None: - r = z.length().pred() - sort_generator = quick_sort_stepwise(z, l, r) - while True: - try: - next(sort_generator) - except StopIteration: - break + r = len(z) - 1 + if l < r: + q = partition(z, l, r, ctx) + yield z + yield from quick_sort_stepwise(z, ctx, l, q - 1) + yield from quick_sort_stepwise(z, ctx, q + 1, r) + yield z -def sort_file(filename, sort_func): - z = MemoryArray.create_array_from_file(filename) - sort_func(z) - return z +def partition(z: Array, l: int, r: int, ctx: AlgoContext) -> int: + """ + Lomuto-Partitionierung. + + Wählt z[r] als Pivot. Gibt den endgültigen Pivot-Index zurück. + """ + pivot = Int(z[r].value, ctx) # Pivot-Wert kopieren + ctx.reads += 1 + i = Int(l, ctx) + j = Int(r - 1, ctx) + + while i < j: + while int(i) <= int(j) and z[i] < pivot: + i += 1 + while int(j) >= l and z[j] >= pivot: + j -= 1 + if i < j: + z.swap(int(i), int(j)) + i += 1 + j -= 1 + + if i == j and z[i] < pivot: + i += 1 + if z[i] != pivot: + z.swap(int(i), r) + + return int(i) + + +def quick_sort(z: Array, ctx: AlgoContext, l: int = None, r: int = None): + """Quicksort – vollständige Ausführung ohne Visualisierung.""" + if l is None: + l = 0 + if r is None: + r = len(z) - 1 + for _ in quick_sort_stepwise(z, ctx, l, r): + pass def analyze_complexity(sort_func, sizes, presorted=False): - """ - Analysiert die Komplexität einer Sortierfunktion. - - :param sort_func: Die Funktion, die analysiert wird. - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, -100, 100) - sort_func(random_array) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + sort_func(z, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) - - -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) + ctx.plot_stats(["comparisons", "writes"]) if __name__ == '__main__': sizes = range(10, 101, 5) - #analyze_complexity(quick_sort, sizes) - analyze_complexity(quick_sort, sizes, True) + analyze_complexity(quick_sort, sizes) +# analyze_complexity(quick_sort, sizes, True) diff --git a/vorlesung/L04_besondere_sortierverfahren/count_sorting.py b/vorlesung/L04_besondere_sortierverfahren/count_sorting.py index 507115d..3421664 100644 --- a/vorlesung/L04_besondere_sortierverfahren/count_sorting.py +++ b/vorlesung/L04_besondere_sortierverfahren/count_sorting.py @@ -1,60 +1,53 @@ -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_range import irange +def count_sort(a: Array, b: Array, k: int, ctx: AlgoContext): + """ + Counting Sort. -def count_sort(a: MemoryArray, b: MemoryArray, k: int): - c = MemoryArray(Literal(k + 1)) - for i in mrange(Literal(k + 1)): - c[i].set(Literal(0)) + a – Eingabe-Array mit Werten aus [0, k] + b – Ausgabe-Array (gleiche Länge wie a) + k – maximaler Wert in a + """ + c = Array([0] * (k + 1), ctx) # Zählarray - for j in mrange(a.length()): - c[a[j]].set(c[a[j]].succ()) + # Häufigkeiten zählen + for j in irange(len(a)): + c[a[j]] = c[a[j]] + 1 - for i in mrange(Literal(1), Literal(k + 1)): - c[i].set(int(c[i]) + int(c[i.pred()])) - - for j in mrange(a.length().pred(), Literal(-1), Literal(-1)): - b[c[a[j]].pred()].set(a[j]) - c[a[j]].set(c[a[j]].pred()) + # Kumulierte Summen bilden + for i in irange(1, k + 1): + c[i] = c[i] + c[i - 1] + # Stabil in b einsortieren (rückwärts für Stabilität) + for j in irange(len(a) - 1, -1, -1): + b[c[a[j]] - 1] = a[j] + c[a[j]] = c[a[j]] - 1 def analyze_complexity(sizes, presorted=False): - """ - Analysiert die Komplexität einer Sortierfunktion. - - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen + ctx.reset() if presorted: - random_array = MemoryArray.create_sorted_array(size, 0, 100) + z = Array.sorted(size, ctx) else: - random_array = MemoryArray.create_random_array(size, 0, 100) - dest_array = MemoryArray(Literal(size)) - count_sort(random_array, dest_array, 100) - MemoryManager.save_stats(size) + z = Array.random(size, 0, 100, ctx) + dest = Array([0] * size, ctx) + count_sort(z, dest, 100, ctx) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares", "writes"]) - - -def swap(z: MemoryArray, i: int, j: int): - tmp = z[Literal(i)].value - z[Literal(i)] = z[Literal(j)] - z[Literal(j)].set(tmp) + ctx.plot_stats(["reads", "writes"]) if __name__ == '__main__': - - # Test the count_sort function - a = MemoryArray([2, 5, 3, 0, 2, 3, 0, 3]) - b = MemoryArray(Literal(len(a))) - count_sort(a, b, 5) + ctx = AlgoContext() + a = Array([2, 5, 3, 0, 2, 3, 0, 3], ctx) + b = Array([0] * len(a), ctx) + count_sort(a, b, 5, ctx) + print(b) sizes = range(10, 101, 10) analyze_complexity(sizes) - # analyze_complexity(sizes, True) diff --git a/vorlesung/L05_binaere_baeume/analyze_avl_tree.py b/vorlesung/L05_binaere_baeume/analyze_avl_tree.py index 96b74ca..68b75d7 100644 --- a/vorlesung/L05_binaere_baeume/analyze_avl_tree.py +++ b/vorlesung/L05_binaere_baeume/analyze_avl_tree.py @@ -1,26 +1,22 @@ -from utils.memory_manager import MemoryManager -from utils.memory_array import MemoryArray -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array from vorlesung.L05_binaere_baeume.avl_tree import AVLTree + def analyze_complexity(sizes): - """ - Analysiert die Komplexität - - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen - tree = AVLTree() - random_array = MemoryArray.create_random_array(size, -100, 100) - for i in range(size-1): - tree.insert(int(random_array[Literal(i)])) - MemoryManager.reset() - tree.insert(int(random_array[Literal(size-1)])) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + tree = AVLTree(ctx) + for i in range(size - 1): + tree.insert(z[i].value) + ctx.reset() + tree.insert(z[size - 1].value) + ctx.save_stats(size) + + ctx.plot_stats(["comparisons"]) - MemoryManager.plot_stats(["cells", "compares"]) if __name__ == "__main__": sizes = range(1, 1001, 2) - analyze_complexity(sizes) \ No newline at end of file + analyze_complexity(sizes) diff --git a/vorlesung/L05_binaere_baeume/analyze_binary_tree.py b/vorlesung/L05_binaere_baeume/analyze_binary_tree.py index 4e18f7d..3089c8a 100644 --- a/vorlesung/L05_binaere_baeume/analyze_binary_tree.py +++ b/vorlesung/L05_binaere_baeume/analyze_binary_tree.py @@ -1,26 +1,22 @@ -from utils.memory_manager import MemoryManager -from utils.memory_array import MemoryArray -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree + def analyze_complexity(sizes): - """ - Analysiert die Komplexität - - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen - tree = BinaryTree() - random_array = MemoryArray.create_random_array(size, -100, 100) - for i in range(size-1): - tree.insert(int(random_array[Literal(i)])) - MemoryManager.reset() - tree.insert(int(random_array[Literal(size-1)])) - MemoryManager.save_stats(size) + z = Array.random(size, -100, 100, ctx) + tree = BinaryTree(ctx) + for i in range(size - 1): + tree.insert(z[i].value) + ctx.reset() + tree.insert(z[size - 1].value) + ctx.save_stats(size) + + ctx.plot_stats(["comparisons"]) - MemoryManager.plot_stats(["cells", "compares"]) if __name__ == "__main__": sizes = range(1, 1001, 2) - analyze_complexity(sizes) \ No newline at end of file + analyze_complexity(sizes) diff --git a/vorlesung/L05_binaere_baeume/avl_tree.py b/vorlesung/L05_binaere_baeume/avl_tree.py index e4446d3..7b7eb75 100644 --- a/vorlesung/L05_binaere_baeume/avl_tree.py +++ b/vorlesung/L05_binaere_baeume/avl_tree.py @@ -1,16 +1,16 @@ -from utils.memory_array import MemoryArray from vorlesung.L05_binaere_baeume.avl_tree_node import AVLTreeNode from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree -import logging +from utils.algo_context import AlgoContext +from utils.algo_array import Array + class AVLTree(BinaryTree): - def __init__(self): - super().__init__() + def __init__(self, ctx: AlgoContext): + super().__init__(ctx) def new_node(self, value): - return AVLTreeNode(value) - + return AVLTreeNode(value, self.ctx) def balance(self, node: AVLTreeNode): node.update_balance() @@ -47,7 +47,6 @@ class AVLTree(BinaryTree): self.balance(parent) return node, parent - def delete(self, value): node, parent = super().delete(value) if node: @@ -58,37 +57,25 @@ class AVLTree(BinaryTree): def graph_filename(self): return "AVLTree" + if __name__ == "__main__": + ctx = AlgoContext() + tree = AVLTree(ctx) + + values = Array.from_file("data/seq2.txt", ctx) + for cell in values: + tree.insert(cell.value) def print_node(node, indent=0, level=0): print((indent * 3) * " ", node.value) - tree = AVLTree() - #values = [5, 3, 7, 2, 4, 6, 5, 8] - values = MemoryArray.create_array_from_file("data/seq2.txt") - - for value in values: - tree.insert(value) - - print("In-order traversal:") tree.in_order_traversal(print_node) - print("\nLevel-order traversal:") - tree.level_order_traversal(print_node) print("\nTree structure traversal:") tree.tree_structure_traversal(print_node) - print("\nGraph traversal:") - tree.graph_traversal() tree.insert(9) - tree.graph_traversal() - print("\nDeleting 5:") tree.delete(5) - print("In-order traversal after deletion:") tree.in_order_traversal(print_node) - print("\nLevel-order traversal after deletion:") - tree.level_order_traversal(print_node) - print("\nTree structure traversal after deletion:") - tree.tree_structure_traversal(print_node) diff --git a/vorlesung/L05_binaere_baeume/avl_tree_game.py b/vorlesung/L05_binaere_baeume/avl_tree_game.py index d726bb7..0dedd14 100644 --- a/vorlesung/L05_binaere_baeume/avl_tree_game.py +++ b/vorlesung/L05_binaere_baeume/avl_tree_game.py @@ -1,14 +1,14 @@ import random import pygame from utils.game import Game +from utils.algo_context import AlgoContext from avl_tree import AVLTree WHITE = (255, 255, 255) -BLUE = (0, 0, 255) -BLACK = (0, 0, 0) -WIDTH = 800 -HEIGHT = 400 -MARGIN = 20 +BLUE = (0, 0, 255) +BLACK = (0, 0, 0) +WIDTH, HEIGHT, MARGIN = 800, 400, 20 + class AVLTreeGame(Game): @@ -18,8 +18,12 @@ class AVLTreeGame(Game): self.z = list(range(1, 501)) random.shuffle(self.z) self.finished = False - self.tree = AVLTree() - self.tree.get_height = lambda node: 0 if node is None else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right)) + self.ctx = AlgoContext() + self.tree = AVLTree(self.ctx) + self.tree.get_height = lambda node: ( + 0 if node is None + else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right)) + ) self.height = self.tree.get_height(self.tree.root) self.generator = None @@ -43,7 +47,7 @@ class AVLTreeGame(Game): super().draw_game() def draw_tree(self, node, x, y, x_offset): - y_offset = (HEIGHT - (2 * MARGIN)) / self.height + y_offset = (HEIGHT - 2 * MARGIN) / self.height if node is not None: pygame.draw.circle(self.screen, BLUE, (x, y), 2) if node.left is not None: @@ -53,7 +57,7 @@ class AVLTreeGame(Game): pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset)) self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2) + if __name__ == "__main__": tree_game = AVLTreeGame() tree_game.run() - diff --git a/vorlesung/L05_binaere_baeume/avl_tree_node.py b/vorlesung/L05_binaere_baeume/avl_tree_node.py index ff4ccb9..0a3a762 100644 --- a/vorlesung/L05_binaere_baeume/avl_tree_node.py +++ b/vorlesung/L05_binaere_baeume/avl_tree_node.py @@ -1,10 +1,13 @@ from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode +from utils.algo_context import AlgoContext + class AVLTreeNode(BinaryTreeNode): - def __init__(self, value): - super().__init__(value) + + def __init__(self, value, ctx: AlgoContext): + super().__init__(value, ctx) self.parent = None - self.balance = 0 + self.balance = 0 # plain int – Metadaten, kein Zähler def __repr__(self): return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})" @@ -13,7 +16,7 @@ class AVLTreeNode(BinaryTreeNode): dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!", xlabel=str(self.balance)) def update_balance(self): - left_height = self.left.height() if self.left else 0 + left_height = self.left.height() if self.left else 0 right_height = self.right.height() if self.right else 0 self.balance = right_height - left_height @@ -58,4 +61,3 @@ class AVLTreeNode(BinaryTreeNode): def left_right_rotate(self): self.left = self.left.left_rotate() return self.right_rotate() - diff --git a/vorlesung/L05_binaere_baeume/bin_search.py b/vorlesung/L05_binaere_baeume/bin_search.py index 1bb0299..412c41d 100644 --- a/vorlesung/L05_binaere_baeume/bin_search.py +++ b/vorlesung/L05_binaere_baeume/bin_search.py @@ -1,61 +1,53 @@ import random - -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_manager import MemoryManager -from utils.memory_range import mrange -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int -def binary_search(z: MemoryArray, s: MemoryCell, l: Literal = None, r: Literal = None): +def binary_search(z: Array, s: Int, l: int = None, r: int = None): """ - Perform a binary search on the sorted array z for the value x. + Binäre Suche auf dem sortierten Array z nach dem Wert s. + + l, r – 0-basierte Grenzen (plain int, optional). + Gibt den Index als plain int zurück, oder None wenn nicht gefunden. """ if l is None: - l = Literal(0) + l = 0 if r is None: - r = Literal(z.length().pred()) + r = len(z) - 1 if l > r: return None - with MemoryCell(l) as m: - m += r - m //= Literal(2) - if s < z[m]: - return binary_search(z, s, l, m.pred()) - elif s > z[m]: - return binary_search(z, s, m.succ(), r) - else: - return m + + m = Int((l + r) // 2, s._ctx) + if s < z[m]: + return binary_search(z, s, l, int(m) - 1) + elif s > z[m]: + return binary_search(z, s, int(m) + 1, r) + else: + return int(m) def analyze_complexity(sizes): - """ - Analysiert die Komplexität - - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen - random_array = MemoryArray.create_sorted_array(size) - search_value = random.randint(-100, 100) - binary_search(random_array, MemoryCell(search_value)) - MemoryManager.save_stats(size) - - MemoryManager.plot_stats(["cells", "compares", "adds"]) - + ctx.reset() + z = Array.sorted(size, ctx) + search_value = Int(random.randint(0, size - 1), ctx) + binary_search(z, search_value) + ctx.save_stats(size) + ctx.plot_stats(["comparisons", "additions"]) if __name__ == "__main__": - # Example usage - arr = MemoryArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - search_value = MemoryCell(8) - result = binary_search(arr, search_value) + ctx = AlgoContext() + arr = Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ctx) + s = Int(8, ctx) + result = binary_search(arr, s) if result is not None: - print(f"Value {search_value} found at index {result}.") + print(f"Value {s} found at index {result}.") else: - print(f"Value {search_value} not found in the array.") - + print(f"Value {s} not found.") sizes = range(1, 1001, 2) analyze_complexity(sizes) diff --git a/vorlesung/L05_binaere_baeume/bin_tree.py b/vorlesung/L05_binaere_baeume/bin_tree.py index e0b83cf..87d3265 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree.py +++ b/vorlesung/L05_binaere_baeume/bin_tree.py @@ -1,4 +1,5 @@ from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode +from utils.algo_context import AlgoContext from utils.project_dir import get_path from datetime import datetime import graphviz @@ -6,12 +7,13 @@ import graphviz class BinaryTree: - def __init__(self): + def __init__(self, ctx: AlgoContext): self.root = None self.size = 0 + self.ctx = ctx def new_node(self, value): - return BinaryTreeNode(value) + return BinaryTreeNode(value, self.ctx) def insert(self, value): self.size += 1 @@ -50,9 +52,6 @@ class BinaryTree: return None def delete(self, value): - # Der Wert wird im Baum gesucht und der erste Treffer gelöscht - # Rückgabe falls der Wert gefunden wird: - # der Knoten, der den zu löschenden Knoten ersetzt und der Elternknoten des gelöschten Knotens parent = None current = self.root value = self.new_node(value) @@ -64,38 +63,25 @@ class BinaryTree: parent = current current = current.right else: - # Knoten gefunden break else: - # Wert nicht gefunden return None, None return self.delete_node(current, parent) def delete_node(self, current, parent): - # Der übergebene Knoten wird - # Rückgabe ist ein Tupel: - # der Knoten, der den zu löschenden Knoten ersetzt und der Elternknoten des gelöschten Knotens self.size -= 1 - # Fall 3: Es gibt zwei Kinder: wir suchen den Nachfolger + # Fall 3: zwei Kinder → Nachfolger suchen if current.left and current.right: parent = current successor = current.right while successor.left: parent = successor successor = successor.left - # Wert des Nachfolgers wird in den Knoten geschrieben, der gelöscht werden soll - current.value = successor.value - # Ab jetzt muss successor gelöscht werden; parent ist bereits richtig gesetzt + current.set(successor) # Wert kopieren (1 read + 1 write) current = successor - # Ermitteln des einen Kindes (falls es eines gibt), sonst None - # Das eine Kind ist der Ersatz für den Knoten, der gelöscht werden soll - if current.left: - child = current.left - else: - child = current.right + child = current.left if current.left else current.right - # Falls es keinen Elternknoten gibt, ist der Ersatzknoten die Wurzel if not parent: self.root = child return child, None @@ -106,17 +92,13 @@ class BinaryTree: parent.right = child return child, parent - def in_order_traversal(self, callback): - - def in_order_traversal_recursive(callback, current): + def _rec(callback, current): if current is not None: - in_order_traversal_recursive(callback, current.left) + _rec(callback, current.left) callback(current) - in_order_traversal_recursive(callback, current.right) - - in_order_traversal_recursive(callback, self.root) - + _rec(callback, current.right) + _rec(callback, self.root) def level_order_traversal(self, callback): if self.root is None: @@ -125,23 +107,19 @@ class BinaryTree: while queue: current, level = queue.pop(0) callback(current, level) - if current.left is not None: - queue.append((current.left, level + 1)) - if current.right is not None: - queue.append((current.right, level + 1)) + if current.left is not None: queue.append((current.left, level + 1)) + if current.right is not None: queue.append((current.right, level + 1)) def tree_structure_traversal(self, callback): - - def tree_structure_traversal_recursive(callback, current, level): + def _rec(callback, current, level): nonlocal line if current: - tree_structure_traversal_recursive(callback, current.left, level + 1) + _rec(callback, current.left, level + 1) callback(current, level, line) line += 1 - tree_structure_traversal_recursive(callback, current.right, level + 1) - + _rec(callback, current.right, level + 1) line = 0 - tree_structure_traversal_recursive(callback, self.root, 0) + _rec(callback, self.root, 0) def graph_filename(self): return "BinaryTree" @@ -152,55 +130,44 @@ class BinaryTree: if node is not None: node.graphviz_rep(level, line, dot) - def graph_traversal_recursive(current): + def _rec(current): nonlocal dot if current is not None: if current.left: dot.edge(str(id(current)), str(id(current.left))) - graph_traversal_recursive(current.left) + _rec(current.left) if current.right: dot.edge(str(id(current)), str(id(current.right))) - graph_traversal_recursive(current.right) + _rec(current.right) - dot = graphviz.Digraph( name="BinaryTree", - engine="neato", - node_attr={"shape": "circle", "fontname": "Arial"}, - format="pdf" ) + dot = graphviz.Digraph( + name="BinaryTree", + engine="neato", + node_attr={"shape": "circle", "fontname": "Arial"}, + format="pdf") self.tree_structure_traversal(define_node) - graph_traversal_recursive(self.root) + _rec(self.root) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"{self.graph_filename()}_{timestamp}.gv" - filename = get_path(filename) - dot.render(filename) + dot.render(get_path(f"{self.graph_filename()}_{timestamp}.gv")) + if __name__ == "__main__": - tree = BinaryTree() - values = [5, 3, 7, 2, 4, 6, 5, 8] - - for value in values: - tree.insert(value) + ctx = AlgoContext() + tree = BinaryTree(ctx) + for v in [5, 3, 7, 2, 4, 6, 5, 8]: + tree.insert(v) def print_node(node, indent=0, line=None): print((indent * 3) * " ", node.value) - print("In-order traversal:") tree.in_order_traversal(print_node) print("\nLevel-order traversal:") tree.level_order_traversal(print_node) - print("\nTree structure traversal:") - tree.tree_structure_traversal(print_node) - print("\nGraph traversal:") - tree.graph_traversal() print("\nDeleting 5:") tree.delete(5) - print("In-order traversal after deletion:") tree.in_order_traversal(print_node) - print("\nLevel-order traversal after deletion:") - tree.level_order_traversal(print_node) - print("\nTree structure traversal after deletion:") - tree.tree_structure_traversal(print_node) - + print("\n", ctx) diff --git a/vorlesung/L05_binaere_baeume/bin_tree_game.py b/vorlesung/L05_binaere_baeume/bin_tree_game.py index 3aef208..4c74de9 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree_game.py +++ b/vorlesung/L05_binaere_baeume/bin_tree_game.py @@ -1,14 +1,14 @@ import random import pygame from utils.game import Game +from utils.algo_context import AlgoContext from bin_tree import BinaryTree WHITE = (255, 255, 255) -BLUE = (0, 0, 255) -BLACK = (0, 0, 0) -WIDTH = 800 -HEIGHT = 400 -MARGIN = 20 +BLUE = (0, 0, 255) +BLACK = (0, 0, 0) +WIDTH, HEIGHT, MARGIN = 800, 400, 20 + class BinTreeGame(Game): @@ -18,14 +18,17 @@ class BinTreeGame(Game): self.z = list(range(1, 101)) random.shuffle(self.z) self.finished = False - self.tree = BinaryTree() - self.tree.get_height = lambda node: 0 if node is None else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right)) + self.ctx = AlgoContext() + self.tree = BinaryTree(self.ctx) + self.tree.get_height = lambda node: ( + 0 if node is None + else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right)) + ) self.height = self.tree.get_height(self.tree.root) def update_game(self): if not self.finished: - i = self.z.pop() - self.tree.insert(i) + self.tree.insert(self.z.pop()) self.height = self.tree.get_height(self.tree.root) if len(self.z) == 0: self.finished = True @@ -38,7 +41,7 @@ class BinTreeGame(Game): super().draw_game() def draw_tree(self, node, x, y, x_offset): - y_offset = (HEIGHT - (2 * MARGIN)) / self.height + y_offset = (HEIGHT - 2 * MARGIN) / self.height if node is not None: pygame.draw.circle(self.screen, BLUE, (x, y), 2) if node.left is not None: @@ -48,7 +51,7 @@ class BinTreeGame(Game): pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset)) self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2) + if __name__ == "__main__": tree_game = BinTreeGame() tree_game.run() - diff --git a/vorlesung/L05_binaere_baeume/bin_tree_node.py b/vorlesung/L05_binaere_baeume/bin_tree_node.py index d46dcff..8768dff 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree_node.py +++ b/vorlesung/L05_binaere_baeume/bin_tree_node.py @@ -1,14 +1,22 @@ -from utils.memory_cell import MemoryCell +from utils.algo_int import Int +from utils.algo_context import AlgoContext -class BinaryTreeNode(MemoryCell): - def __init__(self, value): - super().__init__(value) +class BinaryTreeNode(Int): + """ + Knoten eines binären Suchbaums. + + Erbt von Int – Vergleiche zwischen Knoten werden automatisch im + AlgoContext gezählt. + """ + + def __init__(self, value, ctx: AlgoContext): + super().__init__(value, ctx) self.left = None self.right = None def height(self): - left_height = self.left.height() if self.left else 0 + left_height = self.left.height() if self.left else 0 right_height = self.right.height() if self.right else 0 return 1 + max(left_height, right_height) @@ -19,4 +27,4 @@ class BinaryTreeNode(MemoryCell): return str(self.value) def graphviz_rep(self, row, col, dot): - dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!") \ No newline at end of file + dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!") diff --git a/vorlesung/L06_b_baeume/analyze_b_tree.py b/vorlesung/L06_b_baeume/analyze_b_tree.py index e142ee8..2defd80 100644 --- a/vorlesung/L06_b_baeume/analyze_b_tree.py +++ b/vorlesung/L06_b_baeume/analyze_b_tree.py @@ -1,58 +1,63 @@ -from utils.memory_manager import MemoryManager -from utils.memory_array import MemoryArray -from utils.literal import Literal +from utils.algo_context import AlgoContext +from utils.algo_array import Array from b_tree import BTree from b_tree_node import BTreeNode -class MemoryManagerBTree(MemoryManager): - """ - Diese Klasse erweitert den MemoryManager, um spezifische Statistiken für B-Bäume zu speichern. - """ - @staticmethod - def count_loads(): - return sum([cell.loaded_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)]) - - @staticmethod - def count_saves(): - return sum([cell.saved_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)]) - - @staticmethod - def save_stats(count): - data = { "cells": MemoryManager.count_cells(), - "reads": MemoryManager.count_reads(), - "writes": MemoryManager.count_writes(), - "compares": MemoryManager.count_compares(), - "adds": MemoryManager.count_adds(), - "subs": MemoryManager.count_subs(), - "muls": MemoryManager.count_muls(), - "divs": MemoryManager.count_divs(), - "bitops": MemoryManager.count_bitops(), - "loads": MemoryManagerBTree.count_loads(), - "saves": MemoryManagerBTree.count_saves() } - MemoryManager.stats[count] = data +def count_loads(root: BTreeNode) -> int: + """Summiert load()-Aufrufe über alle Knoten des Baums.""" + if root is None: + return 0 + total = root.loaded_count + for child in root.children: + if child is not None: + total += count_loads(child) + return total +def count_saves(root: BTreeNode) -> int: + """Summiert save()-Aufrufe über alle Knoten des Baums.""" + if root is None: + return 0 + total = root.saved_count + for child in root.children: + if child is not None: + total += count_saves(child) + return total def analyze_complexity(sizes): - """ - Analysiert die Komplexität + ctx = AlgoContext() + stats: dict[int, dict] = {} - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen - tree = BTree(5) - random_array = MemoryArray.create_random_array(size, -100, 100) - for i in range(size-1): - tree.insert(int(random_array[Literal(i)])) - MemoryManager.reset() - tree.insert(int(random_array[Literal(size-1)])) - MemoryManagerBTree.save_stats(size) + ctx.reset() + z = Array.random(size, -100, 100, ctx) + tree = BTree(5, ctx) + for i in range(size - 1): + tree.insert(z[i]) + ctx.reset() + tree.insert(z[size - 1]) + stats[size] = { + "comparisons": ctx.comparisons, + "writes": ctx.writes, + "loads": count_loads(tree.root), + "saves": count_saves(tree.root), + } + + # Einfaches Liniendiagramm über alle gespeicherten Metriken + import matplotlib.pyplot as plt + x = list(stats.keys()) + fig, axes = plt.subplots(len(stats[x[0]]), 1, figsize=(8, 12), sharex=True) + for ax, label in zip(axes, stats[x[0]].keys()): + ax.plot(x, [stats[k][label] for k in x], label=label) + ax.set_ylabel(label) + ax.legend() + plt.xlabel("n") + plt.tight_layout() + plt.show() - MemoryManager.plot_stats(["cells", "compares", "loads", "saves"]) if __name__ == "__main__": sizes = range(1, 1001, 2) - analyze_complexity(sizes) \ No newline at end of file + analyze_complexity(sizes) diff --git a/vorlesung/L06_b_baeume/b_tree.py b/vorlesung/L06_b_baeume/b_tree.py index 7729930..4ca44f9 100644 --- a/vorlesung/L06_b_baeume/b_tree.py +++ b/vorlesung/L06_b_baeume/b_tree.py @@ -1,23 +1,29 @@ -from utils.literal import Literal -from utils.memory_cell import MemoryCell -from utils.memory_array import MemoryArray +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int from b_tree_node import BTreeNode + class BTree: - def __init__(self, m: int): + + def __init__(self, m: int, ctx: AlgoContext): self.m = m - self.root = BTreeNode(m) + self.ctx = ctx + self.root = BTreeNode(m, ctx) + + def _new_node(self): + return BTreeNode(self.m, self.ctx) def search(self, value, start: BTreeNode = None) -> BTreeNode | None: if not start: start = self.root start.load() + if not isinstance(value, Int): + value = Int(value, self.ctx) i = 0 - if not isinstance(value, MemoryCell): - value = MemoryCell(value) - while i < start.n and value > start.value[Literal(i)]: + while i < start.n and value > start.value[i]: i += 1 - if i < start.n and value == start.value[Literal(i)]: + if i < start.n and value == start.value[i]: return start if start.leaf: return None @@ -26,11 +32,11 @@ class BTree: def split_child(self, parent: BTreeNode, i: int): child = parent.children[i] child.load() - h = BTreeNode(self.m) + h = self._new_node() h.leaf = child.leaf h.n = self.m - 1 for j in range(self.m - 1): - h.value[Literal(j)] = child.value[Literal(j + self.m)] + h.value[j] = child.value[j + self.m] if not h.leaf: for j in range(self.m): h.children[j] = child.children[j + self.m] @@ -41,18 +47,18 @@ class BTree: h.save() for j in range(parent.n, i, -1): parent.children[j + 1] = parent.children[j] - parent.value[Literal(j)] = parent.value[Literal(j - 1)] + parent.value[j] = parent.value[j - 1] parent.children[i + 1] = h - parent.value[Literal(i)] = child.value[Literal(self.m - 1)] + parent.value[i] = child.value[self.m - 1] parent.n += 1 parent.save() def insert(self, value): - if not isinstance(value, MemoryCell): - value = MemoryCell(value) + if not isinstance(value, Int): + value = Int(value, self.ctx) r = self.root if r.n == 2 * self.m - 1: - h = BTreeNode(self.m) + h = self._new_node() self.root = h h.leaf = False h.n = 0 @@ -62,44 +68,40 @@ class BTree: else: self.insert_in_node(r, value) - def insert_in_node(self, start: BTreeNode, value): + def insert_in_node(self, start: BTreeNode, value: Int): start.load() i = start.n if start.leaf: - while i >= 1 and value < start.value[Literal(i-1)]: - start.value[Literal(i)] = start.value[Literal(i-1)] + while i >= 1 and value < start.value[i - 1]: + start.value[i] = start.value[i - 1] i -= 1 - start.value[Literal(i)].set(value) + start.value[i] = value start.n += 1 start.save() else: j = 0 - while j < start.n and value > start.value[Literal(j)]: + while j < start.n and value > start.value[j]: j += 1 if start.children[j].n == 2 * self.m - 1: self.split_child(start, j) - if value > start.value[Literal(j)]: + if value > start.value[j]: j += 1 self.insert_in_node(start.children[j], value) def traversal(self, callback): - def traversal_recursive(node, callback): + def _rec(node, callback): i = 0 while i < node.n: if not node.leaf: - traversal_recursive(node.children[i], callback) - callback(node.value[Literal(i)]) + _rec(node.children[i], callback) + callback(node.value[i]) i += 1 if not node.leaf: - traversal_recursive(node.children[i], callback) - - traversal_recursive(self.root, callback) + _rec(node.children[i], callback) + _rec(self.root, callback) def walk(self): - def print_key(key): - print(key, end=" ") - - self.traversal(print_key) + self.traversal(lambda key: print(key, end=" ")) def height(self, start: BTreeNode = None): if not start: @@ -110,8 +112,9 @@ class BTree: if __name__ == "__main__": - a = MemoryArray.create_array_from_file("data/seq3.txt") - tree = BTree(3) + ctx = AlgoContext() + a = Array.from_file("data/seq3.txt", ctx) + tree = BTree(3, ctx) for cell in a: tree.insert(cell) print(f"Height: {tree.height()}") diff --git a/vorlesung/L06_b_baeume/b_tree_node.py b/vorlesung/L06_b_baeume/b_tree_node.py index 20b8bb5..8df5a27 100644 --- a/vorlesung/L06_b_baeume/b_tree_node.py +++ b/vorlesung/L06_b_baeume/b_tree_node.py @@ -1,24 +1,27 @@ -from utils.literal import Literal -from utils.memory_cell import MemoryCell -from utils.memory_array import MemoryArray +from utils.algo_context import AlgoContext +from utils.algo_array import Array -class BTreeNode(MemoryCell): - def __init__(self, m: int): - super().__init__() +class BTreeNode: + """ + Knoten eines B-Baums. + + value – Array der Schlüssel (Kapazität: 2m-1) + n – Anzahl aktuell gespeicherter Schlüssel + leaf – True wenn Blattknoten + loaded_count / saved_count – Disk-I/O-Zähler für externe Komplexitätsanalyse + """ + + def __init__(self, m: int, ctx: AlgoContext): self.m = m + self.ctx = ctx self.n = 0 self.leaf = True - self.value = MemoryArray(Literal(2 * m - 1)) + self.value = Array([0] * (2 * m - 1), ctx) self.children = [None] * (2 * m) self.loaded_count = 0 self.saved_count = 0 - def reset_counters(self): - super().reset_counters() - self.loaded_count = 0 - self.saved_count = 0 - def load(self): self.loaded_count += 1 @@ -26,4 +29,4 @@ class BTreeNode(MemoryCell): self.saved_count += 1 def __str__(self): - return "(" + " ".join([str(self.value[Literal(i)]) for i in range(self.n)]) + ")" \ No newline at end of file + return "(" + " ".join([str(self.value[i]) for i in range(self.n)]) + ")" diff --git a/vorlesung/L07_hashtable/analyze_hashtable.py b/vorlesung/L07_hashtable/analyze_hashtable.py index 441d1bb..5a09e90 100644 --- a/vorlesung/L07_hashtable/analyze_hashtable.py +++ b/vorlesung/L07_hashtable/analyze_hashtable.py @@ -1,58 +1,53 @@ import math import random -from utils.literal import Literal -from utils.memory_cell import MemoryCell -from utils.memory_array import MemoryArray -from utils.memory_manager import MemoryManager +from utils.algo_context import AlgoContext +from utils.algo_int import Int +from utils.algo_array import Array from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing -#Goldener Schnitt -a = Literal((math.sqrt(5) - 1) / 2) +# Goldener Schnitt (Konstante, nicht instrumentiert) +_A = (math.sqrt(5) - 1) / 2 -# Hashfunktion nach multiplikativer Methode -def h(x: MemoryCell, m: Literal) -> Literal: - with MemoryCell(int(x * a)) as integer_part, MemoryCell(x * a) as full_product: - with MemoryCell(full_product - integer_part) as fractional_part: - return Literal(abs(int(fractional_part * m))) -# Quadratische Sondierung -def f(x: MemoryCell, i: Literal, m: Literal) -> Literal: - c1 = 1 - c2 = 5 - with MemoryCell(h(x, m)) as initial_hash, MemoryCell(c2 * int(i) * int(i)) as quadratic_offset: - with MemoryCell(initial_hash + quadratic_offset) as probe_position: - probe_position += Literal(c1 * int(i)) # Linear component - return probe_position % m +def h(x: Int, m: Int) -> Int: + """Hashfunktion nach multiplikativer Methode.""" + full = x.value * _A + return Int(int(abs(full - int(full)) * int(m)), x._ctx) -# Symmetrische quadratische Sondierung -def fs(x: MemoryCell, i: Literal, m: Literal) -> Literal: - with MemoryCell(h(x, m)) as base_hash, MemoryCell(int(i) * int(i)) as square: - if int(i) % 2 == 0: # gerades i: Vorwärtssondierung - with MemoryCell(base_hash + square) as position: - return position % m - else: # ungerades i: Rückwärtssondierung - with MemoryCell(base_hash - square) as position: - return position % m + +def f(x: Int, i: Int, m: Int) -> Int: + """Quadratische Sondierung.""" + c1, c2 = 1, 5 + base = int(h(x, m)) + probe = base + c1 * int(i) + c2 * int(i) ** 2 + return Int(probe % int(m), x._ctx) + + +def fs(x: Int, i: Int, m: Int) -> Int: + """Symmetrische quadratische Sondierung.""" + base = int(h(x, m)) + sq = int(i) ** 2 + if int(i) % 2 == 0: + probe = base + sq + else: + probe = base - sq + return Int(probe % int(m), x._ctx) def analyze_complexity(sizes): - """ - Analysiert die Komplexität - - :param sizes: Eine Liste von Eingabegrößen für die Analyse. - """ + ctx = AlgoContext() for size in sizes: - MemoryManager.purge() # Speicher zurücksetzen - ht = HashTableOpenAddressing(size, f) - random_array = MemoryArray.create_random_array(size, -100, 100) - for cell in random_array: + ctx.reset() + ht = HashTableOpenAddressing(size, f, ctx) + z = Array.random(size, -100, 100, ctx) + for cell in z: ht.insert(cell) - MemoryManager.reset() - cell = random.choice(random_array.cells) - ht.search(cell) - MemoryManager.save_stats(size) + ctx.reset() + target = z[random.randint(0, size - 1)] + ht.search(target) + ctx.save_stats(size) - MemoryManager.plot_stats(["cells", "compares"]) + ctx.plot_stats(["comparisons"]) if __name__ == "__main__": diff --git a/vorlesung/L07_hashtable/hashtable.py b/vorlesung/L07_hashtable/hashtable.py index d2b7abd..9e27d79 100644 --- a/vorlesung/L07_hashtable/hashtable.py +++ b/vorlesung/L07_hashtable/hashtable.py @@ -1,76 +1,75 @@ from collections.abc import Callable -from utils.literal import Literal -from utils.memory_array import MemoryArray -from utils.memory_cell import MemoryCell -from utils.memory_range import mrange +from utils.algo_context import AlgoContext +from utils.algo_array import Array +from utils.algo_int import Int +from utils.algo_range import irange -UNUSED_MARK = "UNUSED" +UNUSED_MARK = "UNUSED" DELETED_MARK = "DELETED" + class HashTableOpenAddressing: - def __init__(self, m: Literal, f: Callable[[MemoryCell, Literal, Literal], Literal]): - if not isinstance(m, Literal): - m = Literal(m) - self.m = m + """ + Hashtabelle mit offener Adressierung. + + f – Sondierungsfunktion f(x: Int, i: Int, m: Int) -> Int + Liefert die Tabellenposition für Schlüssel x beim i-ten Versuch. + """ + + def __init__(self, m: int, f: Callable[[Int, Int, Int], Int], ctx: AlgoContext): + self.ctx = ctx + self.m = Int(m, ctx) self.f = f - self.table = MemoryArray(m) - for i in mrange(m): - self.table[i].value = UNUSED_MARK + self.table = Array([UNUSED_MARK] * m, ctx) - def insert(self, x: MemoryCell): - with MemoryCell(0) as i: - while i < self.m: - j = self.f(x, i, self.m) - if self.is_free(j): - self.table[j].set(x) - return True - i.set(i.succ()) + def insert(self, x: Int) -> bool: + i = Int(0, self.ctx) + while i < self.m: + j = self.f(x, i, self.m) + if self.is_free(j): + self.table[j] = x + return True + i += 1 return False - def search(self, x: MemoryCell): - with MemoryCell(0) as i: - while i < self.m: - j = self.f(x, i, self.m) - if self.is_unused(j): - return False - if self.table[j] == x: - return True - i.set(i.succ()) + def search(self, x: Int) -> bool: + i = Int(0, self.ctx) + while i < self.m: + j = self.f(x, i, self.m) + if self.is_unused(j): + return False + if self.table[j] == x: + return True + i += 1 return False - def delete(self, x: MemoryCell): - with MemoryCell(0) as i: - while i < self.m: - j = self.f(x, i, self.m) - if self.is_unused(j): - return False - if self.table[j] == x: - self.table[j].value = DELETED_MARK - return True - i.set(i.succ()) + def delete(self, x: Int) -> bool: + i = Int(0, self.ctx) + while i < self.m: + j = self.f(x, i, self.m) + if self.is_unused(j): + return False + if self.table[j] == x: + self.table[j].set(DELETED_MARK) # Tombstone setzen (1 write) + return True + i += 1 return False def __str__(self): return str(self.table) - def alpha(self): - with MemoryCell(0) as i: - used = 0 - while i < self.m: - used += 0 if self.is_free(i) else 1 - i.set(i.succ()) + def alpha(self) -> float: + """Belegungsfaktor der Tabelle.""" + used = sum(0 if self.is_free(Int(i, self.ctx)) else 1 + for i in range(int(self.m))) return used / int(self.m) - def is_unused(self, i: Literal): - if self.table[i].value == UNUSED_MARK: - return True - return False + def is_unused(self, i: Int) -> bool: + return self.table[i].value == UNUSED_MARK - def is_deleted(self, i: Literal): - if self.table[i].value == DELETED_MARK: - return True - return False + def is_deleted(self, i: Int) -> bool: + return self.table[i].value == DELETED_MARK - def is_free(self, i: Literal): + def is_free(self, i: Int) -> bool: return self.is_unused(i) or self.is_deleted(i)