Compare commits
2 Commits
4f5a78ac05
...
c48b5c7e59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c48b5c7e59 | ||
|
|
228273f399 |
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Claude Code metadata
|
||||||
|
CLAUDE.md
|
||||||
|
.claude/
|
||||||
|
specs/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
@ -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
|
||||||
139
utils/algo_array.py
Normal file
139
utils/algo_array.py
Normal file
@ -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)
|
||||||
135
utils/algo_context.py
Normal file
135
utils/algo_context.py
Normal file
@ -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()
|
||||||
319
utils/algo_int.py
Normal file
319
utils/algo_int.py
Normal file
@ -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})"
|
||||||
51
utils/algo_range.py
Normal file
51
utils/algo_range.py
Normal file
@ -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
|
||||||
@ -1,5 +0,0 @@
|
|||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
MAX_VALUE = Literal(99999999999999999999)
|
|
||||||
MIN_VALUE = Literal(-99999999999999999999)
|
|
||||||
|
|
||||||
103
utils/literal.py
103
utils/literal.py
@ -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)
|
|
||||||
|
|
||||||
@ -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)
|
|
||||||
|
|
||||||
@ -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.")
|
|
||||||
@ -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())
|
|
||||||
|
|
||||||
@ -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)
|
|
||||||
175
utils/test_algo_array.py
Normal file
175
utils/test_algo_array.py
Normal file
@ -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()
|
||||||
193
utils/test_algo_int.py
Normal file
193
utils/test_algo_int.py
Normal file
@ -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()
|
||||||
@ -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)
|
|
||||||
|
|
||||||
@ -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)
|
|
||||||
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
from utils.memory_cell import MemoryCell
|
from utils import AlgoContext, Int
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
x = MemoryCell(int(input("Erste Zahl: ")))
|
ctx = AlgoContext()
|
||||||
y = MemoryCell(int(input("Zweite Zahl: ")))
|
x = Int(int(input("Erste Zahl: ")), ctx)
|
||||||
|
y = Int(int(input("Zweite Zahl: ")), ctx)
|
||||||
|
|
||||||
while x > Literal(0):
|
while x > 0:
|
||||||
if x < y:
|
if x < y:
|
||||||
x, y = y, x
|
x, y = y, x
|
||||||
x -= y
|
x -= y
|
||||||
print(y)
|
print(y)
|
||||||
|
|
||||||
print(f"Insgesamt gab es {x.sub_count + y.sub_count} Subtraktionen.")
|
print(f"Insgesamt gab es {ctx.subtractions} Subtraktionen.")
|
||||||
|
|||||||
@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
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
|
from bubble_sorting import bubble_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class BubbleGame(Game):
|
class BubbleGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Bubble Game", fps=60, size=(400, 400))
|
super().__init__("Bubble Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
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):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@ -29,7 +32,7 @@ class BubbleGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@ -38,4 +41,3 @@ class BubbleGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = BubbleGame()
|
b = BubbleGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,80 +1,79 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_range import irange
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def bubble_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def bubble_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
for i in mrange(n.pred()):
|
"""
|
||||||
for j in mrange(n.pred(), i, -1):
|
Bubble Sort – schrittweise Variante (Generator).
|
||||||
if z[j.pred()] > z[j]:
|
|
||||||
swap(z, j, j.pred())
|
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
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort2_stepwise(z: MemoryArray):
|
def bubble_sort2_stepwise(z: Array, ctx: AlgoContext):
|
||||||
n = MemoryCell(z.length())
|
"""
|
||||||
true = Literal(1)
|
Optimierter Bubble Sort mit Frühausstieg – schrittweise Variante.
|
||||||
false = Literal(0)
|
|
||||||
sortiert = MemoryCell()
|
Bricht ab, wenn in einem Durchlauf kein Tausch stattgefunden hat.
|
||||||
|
"""
|
||||||
|
n = len(z)
|
||||||
while True:
|
while True:
|
||||||
sortiert.set(true)
|
swapped = False
|
||||||
for i in mrange(n.pred()):
|
for i in irange(n - 1):
|
||||||
if z[i] > z[i.succ()]:
|
if z[i] > z[i + 1]:
|
||||||
swap(z, i, i.succ())
|
z.swap(i, i + 1)
|
||||||
sortiert.set(false)
|
swapped = True
|
||||||
yield z
|
yield z
|
||||||
n -= Literal(1)
|
n -= 1
|
||||||
if sortiert == true or n <= Literal(1):
|
if not swapped or n <= 1:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort(z: MemoryArray):
|
def bubble_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = bubble_sort_stepwise(z)
|
"""Bubble Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in bubble_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort2(z: MemoryArray):
|
def bubble_sort2(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = bubble_sort2_stepwise(z)
|
"""Optimierter Bubble Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in bubble_sort2_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
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 analyze_complexity(sort_func, sizes, presorted=False):
|
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.
|
Parameters
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
----------
|
||||||
|
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:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
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__':
|
if __name__ == '__main__':
|
||||||
analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
|
|||||||
@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
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
|
from insert_sorting import insert_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class InsertGame(Game):
|
class InsertGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Insert Game", fps=60, size=(400, 400))
|
super().__init__("Insert Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
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):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@ -29,7 +32,7 @@ class InsertGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@ -38,4 +41,3 @@ class InsertGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = InsertGame()
|
b = InsertGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,61 +1,49 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import irange
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def insert_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def insert_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
j = MemoryCell()
|
"""
|
||||||
elem = MemoryCell()
|
Insertion Sort – schrittweise Variante (Generator).
|
||||||
for i in mrange(n):
|
|
||||||
elem.set(z[i])
|
Gibt nach jedem Einfügevorgang den aktuellen Array-Zustand zurück.
|
||||||
j.set(i)
|
"""
|
||||||
while j > Literal(0) and z[j.pred()] > elem:
|
n = len(z)
|
||||||
z[j].set(z[j.pred()])
|
elem = Int(0, ctx) # Zwischenregister für das einzufügende Element
|
||||||
j -= Literal(1)
|
|
||||||
|
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
|
yield z
|
||||||
z[j].set(elem)
|
z[j] = elem # 1 read + 1 write
|
||||||
yield z
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def insert_sort(z: MemoryArray):
|
def insert_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = insert_sort_stepwise(z)
|
"""Insertion Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in insert_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
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 analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
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.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
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__':
|
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])
|
||||||
#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], True)
|
||||||
|
|||||||
@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
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
|
from select_sorting import select_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class SelectGame(Game):
|
class SelectGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Select Game", fps=60, size=(400, 400))
|
super().__init__("Select Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
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):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@ -29,7 +32,7 @@ class SelectGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@ -38,4 +41,3 @@ class SelectGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = SelectGame()
|
b = SelectGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,57 +1,46 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import irange
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def select_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def select_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
cur_min = MemoryCell()
|
"""
|
||||||
for i in mrange(n):
|
Selection Sort – schrittweise Variante (Generator).
|
||||||
cur_min.set(i)
|
|
||||||
for j in mrange(i.succ(), n):
|
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]:
|
if z[j] < z[cur_min]:
|
||||||
cur_min.set(j)
|
cur_min.set(Int(int(j), ctx))
|
||||||
swap(z, i, int(cur_min))
|
z.swap(int(i), int(cur_min))
|
||||||
yield z
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def select_sort(z: MemoryArray):
|
def select_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = select_sort_stepwise(z)
|
"""Selection Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in select_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
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 analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
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.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
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__':
|
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])
|
||||||
|
|||||||
@ -1,23 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.literal import Literal
|
from utils.algo_array import Array
|
||||||
from heap_sorting import heap_sort_stepwise
|
from heap_sorting import heap_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class HeapGame(Game):
|
class HeapGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Heap Game", fps=20, size=(400, 400))
|
super().__init__("Heap Game", fps=20, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
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):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@ -30,7 +32,7 @@ class HeapGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@ -39,4 +41,3 @@ class HeapGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sort_game = HeapGame()
|
sort_game = HeapGame()
|
||||||
sort_game.run()
|
sort_game.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,93 +1,85 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
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()
|
# Heap verwendet 1-basierte Indizierung intern;
|
||||||
yield from make_max_heap(z)
|
# adjust_index() rechnet auf 0-basierte Array-Positionen um.
|
||||||
with MemoryCell(n) as heapsize:
|
|
||||||
for i in mrange(n, 1, -1):
|
def left_child(i: int) -> int:
|
||||||
swap(z, 0, i.pred())
|
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 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
|
yield z
|
||||||
heapsize.set(heapsize.pred())
|
heapsize -= 1
|
||||||
yield from max_heapyfy(z, Literal(1), heapsize)
|
yield from max_heapify(z, 1, heapsize, ctx)
|
||||||
|
|
||||||
|
|
||||||
def heap_sort(z: MemoryArray):
|
def heap_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = heap_sort_stepwise(z)
|
"""Heapsort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in heap_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def left_child(i: Literal):
|
def make_max_heap(z: Array, n: int, ctx: AlgoContext):
|
||||||
return Literal(2 * int(i))
|
"""Baut einen Max-Heap in-place auf."""
|
||||||
|
for i in range(n // 2, 0, -1):
|
||||||
|
yield from max_heapify(z, i, n, ctx)
|
||||||
|
|
||||||
|
|
||||||
def right_child(i: Literal):
|
def max_heapify(z: Array, i: int, heapsize: int, ctx: AlgoContext):
|
||||||
return Literal(2 * int(i) + 1)
|
"""
|
||||||
|
Stellt die Max-Heap-Eigenschaft für den Teilbaum bei Index i wieder her.
|
||||||
|
|
||||||
|
i und heapsize sind plain int (1-basiert). Vergleiche auf Array-Inhalten
|
||||||
def adjust_index(i: Literal):
|
werden über Int automatisch gezählt.
|
||||||
return i.pred()
|
"""
|
||||||
|
|
||||||
|
|
||||||
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_heapyfy(z: MemoryArray, i: Literal, heapsize: Literal):
|
|
||||||
l = left_child(i)
|
l = left_child(i)
|
||||||
r = right_child(i)
|
r = right_child(i)
|
||||||
with MemoryCell(i) as max_value:
|
max_val = i
|
||||||
|
|
||||||
if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]:
|
if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]:
|
||||||
max_value.set(l)
|
max_val = l
|
||||||
if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_value)]:
|
if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_val)]:
|
||||||
max_value.set(r)
|
max_val = r
|
||||||
if max_value != i:
|
|
||||||
swap(z, int(i)-1, int(max_value)-1)
|
if max_val != i:
|
||||||
|
z.swap(adjust_index(i), adjust_index(max_val))
|
||||||
yield z
|
yield z
|
||||||
yield from max_heapyfy(z, max_value, heapsize)
|
yield from max_heapify(z, max_val, heapsize, ctx)
|
||||||
|
|
||||||
|
|
||||||
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):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
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.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
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__':
|
if __name__ == '__main__':
|
||||||
sizes = range(10, 101, 10)
|
sizes = range(10, 101, 10)
|
||||||
analyze_complexity(heap_sort, sizes)
|
analyze_complexity(heap_sort, sizes)
|
||||||
# analyze_complexity(quick_sort, sizes, True)
|
|
||||||
|
|||||||
@ -1,23 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.literal import Literal
|
from utils.algo_array import Array
|
||||||
from quick_sorting import quick_sort_stepwise
|
from quick_sorting import quick_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class QuickGame(Game):
|
class QuickGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Quick Game", fps=10, size=(400, 400))
|
super().__init__("Quick Game", fps=10, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
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):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@ -30,7 +32,7 @@ class QuickGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@ -39,4 +41,3 @@ class QuickGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sort_game = QuickGame()
|
sort_game = QuickGame()
|
||||||
sort_game.run()
|
sort_game.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,81 +1,79 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def partition(z: MemoryArray, l: Literal, r: Literal):
|
def quick_sort_stepwise(z: Array, ctx: AlgoContext, l: int = 0, r: int = None):
|
||||||
with MemoryCell(z[r]) as pivot, MemoryCell(l) as i, MemoryCell(r.pred()) as j:
|
"""
|
||||||
while i < j:
|
Quicksort – schrittweise Variante (Generator).
|
||||||
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)
|
|
||||||
|
|
||||||
|
l, r sind 0-basierte Grenzen (plain int). Alle Vergleiche auf Array-Inhalten
|
||||||
def quick_sort(z: MemoryArray, l: Literal = None, r: Literal = None):
|
werden über Int automatisch gezählt.
|
||||||
if l is None:
|
"""
|
||||||
l = Literal(0)
|
|
||||||
if r is None:
|
if r is None:
|
||||||
r = z.length().pred()
|
r = len(z) - 1
|
||||||
sort_generator = quick_sort_stepwise(z, l, r)
|
if l < r:
|
||||||
while True:
|
q = partition(z, l, r, ctx)
|
||||||
try:
|
yield z
|
||||||
next(sort_generator)
|
yield from quick_sort_stepwise(z, ctx, l, q - 1)
|
||||||
except StopIteration:
|
yield from quick_sort_stepwise(z, ctx, q + 1, r)
|
||||||
break
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
def partition(z: Array, l: int, r: int, ctx: AlgoContext) -> int:
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
"""
|
||||||
sort_func(z)
|
Lomuto-Partitionierung.
|
||||||
return z
|
|
||||||
|
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):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
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.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
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__':
|
if __name__ == '__main__':
|
||||||
sizes = range(10, 101, 5)
|
sizes = range(10, 101, 5)
|
||||||
#analyze_complexity(quick_sort, sizes)
|
analyze_complexity(quick_sort, sizes)
|
||||||
analyze_complexity(quick_sort, sizes, True)
|
# analyze_complexity(quick_sort, sizes, True)
|
||||||
|
|||||||
@ -1,60 +1,53 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_range import irange
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
|
|
||||||
|
def count_sort(a: Array, b: Array, k: int, ctx: AlgoContext):
|
||||||
|
"""
|
||||||
|
Counting Sort.
|
||||||
|
|
||||||
def count_sort(a: MemoryArray, b: MemoryArray, k: int):
|
a – Eingabe-Array mit Werten aus [0, k]
|
||||||
c = MemoryArray(Literal(k + 1))
|
b – Ausgabe-Array (gleiche Länge wie a)
|
||||||
for i in mrange(Literal(k + 1)):
|
k – maximaler Wert in a
|
||||||
c[i].set(Literal(0))
|
"""
|
||||||
|
c = Array([0] * (k + 1), ctx) # Zählarray
|
||||||
|
|
||||||
for j in mrange(a.length()):
|
# Häufigkeiten zählen
|
||||||
c[a[j]].set(c[a[j]].succ())
|
for j in irange(len(a)):
|
||||||
|
c[a[j]] = c[a[j]] + 1
|
||||||
|
|
||||||
for i in mrange(Literal(1), Literal(k + 1)):
|
# Kumulierte Summen bilden
|
||||||
c[i].set(int(c[i]) + int(c[i.pred()]))
|
for i in irange(1, k + 1):
|
||||||
|
c[i] = c[i] + c[i - 1]
|
||||||
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())
|
|
||||||
|
|
||||||
|
# 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):
|
def analyze_complexity(sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size, 0, 100)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, 0, 100)
|
z = Array.random(size, 0, 100, ctx)
|
||||||
dest_array = MemoryArray(Literal(size))
|
dest = Array([0] * size, ctx)
|
||||||
count_sort(random_array, dest_array, 100)
|
count_sort(z, dest, 100, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["reads", "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__':
|
if __name__ == '__main__':
|
||||||
|
ctx = AlgoContext()
|
||||||
# Test the count_sort function
|
a = Array([2, 5, 3, 0, 2, 3, 0, 3], ctx)
|
||||||
a = MemoryArray([2, 5, 3, 0, 2, 3, 0, 3])
|
b = Array([0] * len(a), ctx)
|
||||||
b = MemoryArray(Literal(len(a)))
|
count_sort(a, b, 5, ctx)
|
||||||
count_sort(a, b, 5)
|
print(b)
|
||||||
|
|
||||||
sizes = range(10, 101, 10)
|
sizes = range(10, 101, 10)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
# analyze_complexity(sizes, True)
|
|
||||||
|
|||||||
@ -1,25 +1,21 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
z = Array.random(size, -100, 100, ctx)
|
||||||
tree = AVLTree()
|
tree = AVLTree(ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
for i in range(size - 1):
|
||||||
for i in range(size-1):
|
tree.insert(z[i].value)
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
ctx.reset()
|
||||||
MemoryManager.reset()
|
tree.insert(z[size - 1].value)
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
ctx.save_stats(size)
|
||||||
MemoryManager.save_stats(size)
|
|
||||||
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
|
|||||||
@ -1,25 +1,21 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
z = Array.random(size, -100, 100, ctx)
|
||||||
tree = BinaryTree()
|
tree = BinaryTree(ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
for i in range(size - 1):
|
||||||
for i in range(size-1):
|
tree.insert(z[i].value)
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
ctx.reset()
|
||||||
MemoryManager.reset()
|
tree.insert(z[size - 1].value)
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
ctx.save_stats(size)
|
||||||
MemoryManager.save_stats(size)
|
|
||||||
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
|
|||||||
@ -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.avl_tree_node import AVLTreeNode
|
||||||
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
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):
|
class AVLTree(BinaryTree):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ctx: AlgoContext):
|
||||||
super().__init__()
|
super().__init__(ctx)
|
||||||
|
|
||||||
def new_node(self, value):
|
def new_node(self, value):
|
||||||
return AVLTreeNode(value)
|
return AVLTreeNode(value, self.ctx)
|
||||||
|
|
||||||
|
|
||||||
def balance(self, node: AVLTreeNode):
|
def balance(self, node: AVLTreeNode):
|
||||||
node.update_balance()
|
node.update_balance()
|
||||||
@ -47,7 +47,6 @@ class AVLTree(BinaryTree):
|
|||||||
self.balance(parent)
|
self.balance(parent)
|
||||||
return node, parent
|
return node, parent
|
||||||
|
|
||||||
|
|
||||||
def delete(self, value):
|
def delete(self, value):
|
||||||
node, parent = super().delete(value)
|
node, parent = super().delete(value)
|
||||||
if node:
|
if node:
|
||||||
@ -58,37 +57,25 @@ class AVLTree(BinaryTree):
|
|||||||
def graph_filename(self):
|
def graph_filename(self):
|
||||||
return "AVLTree"
|
return "AVLTree"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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):
|
def print_node(node, indent=0, level=0):
|
||||||
print((indent * 3) * " ", node.value)
|
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:")
|
print("In-order traversal:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal:")
|
|
||||||
tree.level_order_traversal(print_node)
|
|
||||||
print("\nTree structure traversal:")
|
print("\nTree structure traversal:")
|
||||||
tree.tree_structure_traversal(print_node)
|
tree.tree_structure_traversal(print_node)
|
||||||
print("\nGraph traversal:")
|
|
||||||
tree.graph_traversal()
|
|
||||||
|
|
||||||
tree.insert(9)
|
tree.insert(9)
|
||||||
tree.graph_traversal()
|
|
||||||
|
|
||||||
print("\nDeleting 5:")
|
print("\nDeleting 5:")
|
||||||
tree.delete(5)
|
tree.delete(5)
|
||||||
|
|
||||||
print("In-order traversal after deletion:")
|
print("In-order traversal after deletion:")
|
||||||
tree.in_order_traversal(print_node)
|
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)
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.game import Game
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from avl_tree import AVLTree
|
from avl_tree import AVLTree
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WIDTH = 800
|
WIDTH, HEIGHT, MARGIN = 800, 400, 20
|
||||||
HEIGHT = 400
|
|
||||||
MARGIN = 20
|
|
||||||
|
|
||||||
class AVLTreeGame(Game):
|
class AVLTreeGame(Game):
|
||||||
|
|
||||||
@ -18,8 +18,12 @@ class AVLTreeGame(Game):
|
|||||||
self.z = list(range(1, 501))
|
self.z = list(range(1, 501))
|
||||||
random.shuffle(self.z)
|
random.shuffle(self.z)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.tree = AVLTree()
|
self.ctx = AlgoContext()
|
||||||
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.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.height = self.tree.get_height(self.tree.root)
|
||||||
self.generator = None
|
self.generator = None
|
||||||
|
|
||||||
@ -43,7 +47,7 @@ class AVLTreeGame(Game):
|
|||||||
super().draw_game()
|
super().draw_game()
|
||||||
|
|
||||||
def draw_tree(self, node, x, y, x_offset):
|
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:
|
if node is not None:
|
||||||
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
||||||
if node.left is not None:
|
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))
|
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)
|
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree_game = AVLTreeGame()
|
tree_game = AVLTreeGame()
|
||||||
tree_game.run()
|
tree_game.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
|
||||||
|
|
||||||
class AVLTreeNode(BinaryTreeNode):
|
class AVLTreeNode(BinaryTreeNode):
|
||||||
def __init__(self, value):
|
|
||||||
super().__init__(value)
|
def __init__(self, value, ctx: AlgoContext):
|
||||||
|
super().__init__(value, ctx)
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.balance = 0
|
self.balance = 0 # plain int – Metadaten, kein Zähler
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})"
|
return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})"
|
||||||
@ -58,4 +61,3 @@ class AVLTreeNode(BinaryTreeNode):
|
|||||||
def left_right_rotate(self):
|
def left_right_rotate(self):
|
||||||
self.left = self.left.left_rotate()
|
self.left = self.left.left_rotate()
|
||||||
return self.right_rotate()
|
return self.right_rotate()
|
||||||
|
|
||||||
|
|||||||
@ -1,61 +1,53 @@
|
|||||||
import random
|
import random
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
if l is None:
|
||||||
l = Literal(0)
|
l = 0
|
||||||
if r is None:
|
if r is None:
|
||||||
r = Literal(z.length().pred())
|
r = len(z) - 1
|
||||||
if l > r:
|
if l > r:
|
||||||
return None
|
return None
|
||||||
with MemoryCell(l) as m:
|
|
||||||
m += r
|
m = Int((l + r) // 2, s._ctx)
|
||||||
m //= Literal(2)
|
|
||||||
if s < z[m]:
|
if s < z[m]:
|
||||||
return binary_search(z, s, l, m.pred())
|
return binary_search(z, s, l, int(m) - 1)
|
||||||
elif s > z[m]:
|
elif s > z[m]:
|
||||||
return binary_search(z, s, m.succ(), r)
|
return binary_search(z, s, int(m) + 1, r)
|
||||||
else:
|
else:
|
||||||
return m
|
return int(m)
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
search_value = random.randint(-100, 100)
|
search_value = Int(random.randint(0, size - 1), ctx)
|
||||||
binary_search(random_array, MemoryCell(search_value))
|
binary_search(z, search_value)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "adds"])
|
|
||||||
|
|
||||||
|
|
||||||
|
ctx.plot_stats(["comparisons", "additions"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Example usage
|
ctx = AlgoContext()
|
||||||
arr = MemoryArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
arr = Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ctx)
|
||||||
search_value = MemoryCell(8)
|
s = Int(8, ctx)
|
||||||
result = binary_search(arr, search_value)
|
result = binary_search(arr, s)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
print(f"Value {search_value} found at index {result}.")
|
print(f"Value {s} found at index {result}.")
|
||||||
else:
|
else:
|
||||||
print(f"Value {search_value} not found in the array.")
|
print(f"Value {s} not found.")
|
||||||
|
|
||||||
|
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from utils.project_dir import get_path
|
from utils.project_dir import get_path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import graphviz
|
import graphviz
|
||||||
@ -6,12 +7,13 @@ import graphviz
|
|||||||
|
|
||||||
class BinaryTree:
|
class BinaryTree:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ctx: AlgoContext):
|
||||||
self.root = None
|
self.root = None
|
||||||
self.size = 0
|
self.size = 0
|
||||||
|
self.ctx = ctx
|
||||||
|
|
||||||
def new_node(self, value):
|
def new_node(self, value):
|
||||||
return BinaryTreeNode(value)
|
return BinaryTreeNode(value, self.ctx)
|
||||||
|
|
||||||
def insert(self, value):
|
def insert(self, value):
|
||||||
self.size += 1
|
self.size += 1
|
||||||
@ -50,9 +52,6 @@ class BinaryTree:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def delete(self, value):
|
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
|
parent = None
|
||||||
current = self.root
|
current = self.root
|
||||||
value = self.new_node(value)
|
value = self.new_node(value)
|
||||||
@ -64,38 +63,25 @@ class BinaryTree:
|
|||||||
parent = current
|
parent = current
|
||||||
current = current.right
|
current = current.right
|
||||||
else:
|
else:
|
||||||
# Knoten gefunden
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Wert nicht gefunden
|
|
||||||
return None, None
|
return None, None
|
||||||
return self.delete_node(current, parent)
|
return self.delete_node(current, parent)
|
||||||
|
|
||||||
def delete_node(self, 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
|
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:
|
if current.left and current.right:
|
||||||
parent = current
|
parent = current
|
||||||
successor = current.right
|
successor = current.right
|
||||||
while successor.left:
|
while successor.left:
|
||||||
parent = successor
|
parent = successor
|
||||||
successor = successor.left
|
successor = successor.left
|
||||||
# Wert des Nachfolgers wird in den Knoten geschrieben, der gelöscht werden soll
|
current.set(successor) # Wert kopieren (1 read + 1 write)
|
||||||
current.value = successor.value
|
|
||||||
# Ab jetzt muss successor gelöscht werden; parent ist bereits richtig gesetzt
|
|
||||||
current = successor
|
current = successor
|
||||||
|
|
||||||
# Ermitteln des einen Kindes (falls es eines gibt), sonst None
|
child = current.left if current.left else current.right
|
||||||
# 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
|
|
||||||
|
|
||||||
# Falls es keinen Elternknoten gibt, ist der Ersatzknoten die Wurzel
|
|
||||||
if not parent:
|
if not parent:
|
||||||
self.root = child
|
self.root = child
|
||||||
return child, None
|
return child, None
|
||||||
@ -106,17 +92,13 @@ class BinaryTree:
|
|||||||
parent.right = child
|
parent.right = child
|
||||||
return child, parent
|
return child, parent
|
||||||
|
|
||||||
|
|
||||||
def in_order_traversal(self, callback):
|
def in_order_traversal(self, callback):
|
||||||
|
def _rec(callback, current):
|
||||||
def in_order_traversal_recursive(callback, current):
|
|
||||||
if current is not None:
|
if current is not None:
|
||||||
in_order_traversal_recursive(callback, current.left)
|
_rec(callback, current.left)
|
||||||
callback(current)
|
callback(current)
|
||||||
in_order_traversal_recursive(callback, current.right)
|
_rec(callback, current.right)
|
||||||
|
_rec(callback, self.root)
|
||||||
in_order_traversal_recursive(callback, self.root)
|
|
||||||
|
|
||||||
|
|
||||||
def level_order_traversal(self, callback):
|
def level_order_traversal(self, callback):
|
||||||
if self.root is None:
|
if self.root is None:
|
||||||
@ -125,23 +107,19 @@ class BinaryTree:
|
|||||||
while queue:
|
while queue:
|
||||||
current, level = queue.pop(0)
|
current, level = queue.pop(0)
|
||||||
callback(current, level)
|
callback(current, level)
|
||||||
if current.left is not None:
|
if current.left is not None: queue.append((current.left, level + 1))
|
||||||
queue.append((current.left, level + 1))
|
if current.right is not None: queue.append((current.right, level + 1))
|
||||||
if current.right is not None:
|
|
||||||
queue.append((current.right, level + 1))
|
|
||||||
|
|
||||||
def tree_structure_traversal(self, callback):
|
def tree_structure_traversal(self, callback):
|
||||||
|
def _rec(callback, current, level):
|
||||||
def tree_structure_traversal_recursive(callback, current, level):
|
|
||||||
nonlocal line
|
nonlocal line
|
||||||
if current:
|
if current:
|
||||||
tree_structure_traversal_recursive(callback, current.left, level + 1)
|
_rec(callback, current.left, level + 1)
|
||||||
callback(current, level, line)
|
callback(current, level, line)
|
||||||
line += 1
|
line += 1
|
||||||
tree_structure_traversal_recursive(callback, current.right, level + 1)
|
_rec(callback, current.right, level + 1)
|
||||||
|
|
||||||
line = 0
|
line = 0
|
||||||
tree_structure_traversal_recursive(callback, self.root, 0)
|
_rec(callback, self.root, 0)
|
||||||
|
|
||||||
def graph_filename(self):
|
def graph_filename(self):
|
||||||
return "BinaryTree"
|
return "BinaryTree"
|
||||||
@ -152,55 +130,44 @@ class BinaryTree:
|
|||||||
if node is not None:
|
if node is not None:
|
||||||
node.graphviz_rep(level, line, dot)
|
node.graphviz_rep(level, line, dot)
|
||||||
|
|
||||||
def graph_traversal_recursive(current):
|
def _rec(current):
|
||||||
nonlocal dot
|
nonlocal dot
|
||||||
if current is not None:
|
if current is not None:
|
||||||
if current.left:
|
if current.left:
|
||||||
dot.edge(str(id(current)), str(id(current.left)))
|
dot.edge(str(id(current)), str(id(current.left)))
|
||||||
graph_traversal_recursive(current.left)
|
_rec(current.left)
|
||||||
if current.right:
|
if current.right:
|
||||||
dot.edge(str(id(current)), str(id(current.right)))
|
dot.edge(str(id(current)), str(id(current.right)))
|
||||||
graph_traversal_recursive(current.right)
|
_rec(current.right)
|
||||||
|
|
||||||
dot = graphviz.Digraph( name="BinaryTree",
|
dot = graphviz.Digraph(
|
||||||
|
name="BinaryTree",
|
||||||
engine="neato",
|
engine="neato",
|
||||||
node_attr={"shape": "circle", "fontname": "Arial"},
|
node_attr={"shape": "circle", "fontname": "Arial"},
|
||||||
format="pdf" )
|
format="pdf")
|
||||||
self.tree_structure_traversal(define_node)
|
self.tree_structure_traversal(define_node)
|
||||||
graph_traversal_recursive(self.root)
|
_rec(self.root)
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"{self.graph_filename()}_{timestamp}.gv"
|
dot.render(get_path(f"{self.graph_filename()}_{timestamp}.gv"))
|
||||||
filename = get_path(filename)
|
|
||||||
dot.render(filename)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree = BinaryTree()
|
ctx = AlgoContext()
|
||||||
values = [5, 3, 7, 2, 4, 6, 5, 8]
|
tree = BinaryTree(ctx)
|
||||||
|
for v in [5, 3, 7, 2, 4, 6, 5, 8]:
|
||||||
for value in values:
|
tree.insert(v)
|
||||||
tree.insert(value)
|
|
||||||
|
|
||||||
def print_node(node, indent=0, line=None):
|
def print_node(node, indent=0, line=None):
|
||||||
print((indent * 3) * " ", node.value)
|
print((indent * 3) * " ", node.value)
|
||||||
|
|
||||||
|
|
||||||
print("In-order traversal:")
|
print("In-order traversal:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal:")
|
print("\nLevel-order traversal:")
|
||||||
tree.level_order_traversal(print_node)
|
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:")
|
print("\nDeleting 5:")
|
||||||
tree.delete(5)
|
tree.delete(5)
|
||||||
|
|
||||||
print("In-order traversal after deletion:")
|
print("In-order traversal after deletion:")
|
||||||
tree.in_order_traversal(print_node)
|
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)
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.game import Game
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from bin_tree import BinaryTree
|
from bin_tree import BinaryTree
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WIDTH = 800
|
WIDTH, HEIGHT, MARGIN = 800, 400, 20
|
||||||
HEIGHT = 400
|
|
||||||
MARGIN = 20
|
|
||||||
|
|
||||||
class BinTreeGame(Game):
|
class BinTreeGame(Game):
|
||||||
|
|
||||||
@ -18,14 +18,17 @@ class BinTreeGame(Game):
|
|||||||
self.z = list(range(1, 101))
|
self.z = list(range(1, 101))
|
||||||
random.shuffle(self.z)
|
random.shuffle(self.z)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.tree = BinaryTree()
|
self.ctx = AlgoContext()
|
||||||
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.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)
|
self.height = self.tree.get_height(self.tree.root)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
i = self.z.pop()
|
self.tree.insert(self.z.pop())
|
||||||
self.tree.insert(i)
|
|
||||||
self.height = self.tree.get_height(self.tree.root)
|
self.height = self.tree.get_height(self.tree.root)
|
||||||
if len(self.z) == 0:
|
if len(self.z) == 0:
|
||||||
self.finished = True
|
self.finished = True
|
||||||
@ -38,7 +41,7 @@ class BinTreeGame(Game):
|
|||||||
super().draw_game()
|
super().draw_game()
|
||||||
|
|
||||||
def draw_tree(self, node, x, y, x_offset):
|
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:
|
if node is not None:
|
||||||
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
||||||
if node.left is not None:
|
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))
|
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)
|
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree_game = BinTreeGame()
|
tree_game = BinTreeGame()
|
||||||
tree_game.run()
|
tree_game.run()
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
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):
|
class BinaryTreeNode(Int):
|
||||||
super().__init__(value)
|
"""
|
||||||
|
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.left = None
|
||||||
self.right = None
|
self.right = None
|
||||||
|
|
||||||
|
|||||||
@ -1,57 +1,62 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from b_tree import BTree
|
from b_tree import BTree
|
||||||
from b_tree_node import BTreeNode
|
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(root: BTreeNode) -> int:
|
||||||
def count_loads():
|
"""Summiert load()-Aufrufe über alle Knoten des Baums."""
|
||||||
return sum([cell.loaded_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
|
if root is None:
|
||||||
|
return 0
|
||||||
@staticmethod
|
total = root.loaded_count
|
||||||
def count_saves():
|
for child in root.children:
|
||||||
return sum([cell.saved_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
|
if child is not None:
|
||||||
|
total += count_loads(child)
|
||||||
@staticmethod
|
return total
|
||||||
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_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):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
stats: dict[int, dict] = {}
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
tree = BTree(5)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
tree = BTree(5, ctx)
|
||||||
for i in range(size-1):
|
for i in range(size - 1):
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
tree.insert(z[i])
|
||||||
MemoryManager.reset()
|
ctx.reset()
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
tree.insert(z[size - 1])
|
||||||
MemoryManagerBTree.save_stats(size)
|
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__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
|
|||||||
@ -1,23 +1,29 @@
|
|||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_int import Int
|
||||||
from b_tree_node import BTreeNode
|
from b_tree_node import BTreeNode
|
||||||
|
|
||||||
|
|
||||||
class BTree:
|
class BTree:
|
||||||
def __init__(self, m: int):
|
|
||||||
|
def __init__(self, m: int, ctx: AlgoContext):
|
||||||
self.m = m
|
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:
|
def search(self, value, start: BTreeNode = None) -> BTreeNode | None:
|
||||||
if not start:
|
if not start:
|
||||||
start = self.root
|
start = self.root
|
||||||
start.load()
|
start.load()
|
||||||
|
if not isinstance(value, Int):
|
||||||
|
value = Int(value, self.ctx)
|
||||||
i = 0
|
i = 0
|
||||||
if not isinstance(value, MemoryCell):
|
while i < start.n and value > start.value[i]:
|
||||||
value = MemoryCell(value)
|
|
||||||
while i < start.n and value > start.value[Literal(i)]:
|
|
||||||
i += 1
|
i += 1
|
||||||
if i < start.n and value == start.value[Literal(i)]:
|
if i < start.n and value == start.value[i]:
|
||||||
return start
|
return start
|
||||||
if start.leaf:
|
if start.leaf:
|
||||||
return None
|
return None
|
||||||
@ -26,11 +32,11 @@ class BTree:
|
|||||||
def split_child(self, parent: BTreeNode, i: int):
|
def split_child(self, parent: BTreeNode, i: int):
|
||||||
child = parent.children[i]
|
child = parent.children[i]
|
||||||
child.load()
|
child.load()
|
||||||
h = BTreeNode(self.m)
|
h = self._new_node()
|
||||||
h.leaf = child.leaf
|
h.leaf = child.leaf
|
||||||
h.n = self.m - 1
|
h.n = self.m - 1
|
||||||
for j in range(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:
|
if not h.leaf:
|
||||||
for j in range(self.m):
|
for j in range(self.m):
|
||||||
h.children[j] = child.children[j + self.m]
|
h.children[j] = child.children[j + self.m]
|
||||||
@ -41,18 +47,18 @@ class BTree:
|
|||||||
h.save()
|
h.save()
|
||||||
for j in range(parent.n, i, -1):
|
for j in range(parent.n, i, -1):
|
||||||
parent.children[j + 1] = parent.children[j]
|
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.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.n += 1
|
||||||
parent.save()
|
parent.save()
|
||||||
|
|
||||||
def insert(self, value):
|
def insert(self, value):
|
||||||
if not isinstance(value, MemoryCell):
|
if not isinstance(value, Int):
|
||||||
value = MemoryCell(value)
|
value = Int(value, self.ctx)
|
||||||
r = self.root
|
r = self.root
|
||||||
if r.n == 2 * self.m - 1:
|
if r.n == 2 * self.m - 1:
|
||||||
h = BTreeNode(self.m)
|
h = self._new_node()
|
||||||
self.root = h
|
self.root = h
|
||||||
h.leaf = False
|
h.leaf = False
|
||||||
h.n = 0
|
h.n = 0
|
||||||
@ -62,44 +68,40 @@ class BTree:
|
|||||||
else:
|
else:
|
||||||
self.insert_in_node(r, value)
|
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()
|
start.load()
|
||||||
i = start.n
|
i = start.n
|
||||||
if start.leaf:
|
if start.leaf:
|
||||||
while i >= 1 and value < start.value[Literal(i-1)]:
|
while i >= 1 and value < start.value[i - 1]:
|
||||||
start.value[Literal(i)] = start.value[Literal(i-1)]
|
start.value[i] = start.value[i - 1]
|
||||||
i -= 1
|
i -= 1
|
||||||
start.value[Literal(i)].set(value)
|
start.value[i] = value
|
||||||
start.n += 1
|
start.n += 1
|
||||||
start.save()
|
start.save()
|
||||||
else:
|
else:
|
||||||
j = 0
|
j = 0
|
||||||
while j < start.n and value > start.value[Literal(j)]:
|
while j < start.n and value > start.value[j]:
|
||||||
j += 1
|
j += 1
|
||||||
if start.children[j].n == 2 * self.m - 1:
|
if start.children[j].n == 2 * self.m - 1:
|
||||||
self.split_child(start, j)
|
self.split_child(start, j)
|
||||||
if value > start.value[Literal(j)]:
|
if value > start.value[j]:
|
||||||
j += 1
|
j += 1
|
||||||
self.insert_in_node(start.children[j], value)
|
self.insert_in_node(start.children[j], value)
|
||||||
|
|
||||||
def traversal(self, callback):
|
def traversal(self, callback):
|
||||||
def traversal_recursive(node, callback):
|
def _rec(node, callback):
|
||||||
i = 0
|
i = 0
|
||||||
while i < node.n:
|
while i < node.n:
|
||||||
if not node.leaf:
|
if not node.leaf:
|
||||||
traversal_recursive(node.children[i], callback)
|
_rec(node.children[i], callback)
|
||||||
callback(node.value[Literal(i)])
|
callback(node.value[i])
|
||||||
i += 1
|
i += 1
|
||||||
if not node.leaf:
|
if not node.leaf:
|
||||||
traversal_recursive(node.children[i], callback)
|
_rec(node.children[i], callback)
|
||||||
|
_rec(self.root, callback)
|
||||||
traversal_recursive(self.root, callback)
|
|
||||||
|
|
||||||
def walk(self):
|
def walk(self):
|
||||||
def print_key(key):
|
self.traversal(lambda key: print(key, end=" "))
|
||||||
print(key, end=" ")
|
|
||||||
|
|
||||||
self.traversal(print_key)
|
|
||||||
|
|
||||||
def height(self, start: BTreeNode = None):
|
def height(self, start: BTreeNode = None):
|
||||||
if not start:
|
if not start:
|
||||||
@ -110,8 +112,9 @@ class BTree:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
a = MemoryArray.create_array_from_file("data/seq3.txt")
|
ctx = AlgoContext()
|
||||||
tree = BTree(3)
|
a = Array.from_file("data/seq3.txt", ctx)
|
||||||
|
tree = BTree(3, ctx)
|
||||||
for cell in a:
|
for cell in a:
|
||||||
tree.insert(cell)
|
tree.insert(cell)
|
||||||
print(f"Height: {tree.height()}")
|
print(f"Height: {tree.height()}")
|
||||||
|
|||||||
@ -1,24 +1,27 @@
|
|||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_array import MemoryArray
|
|
||||||
|
|
||||||
class BTreeNode(MemoryCell):
|
|
||||||
|
|
||||||
def __init__(self, m: int):
|
class BTreeNode:
|
||||||
super().__init__()
|
"""
|
||||||
|
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.m = m
|
||||||
|
self.ctx = ctx
|
||||||
self.n = 0
|
self.n = 0
|
||||||
self.leaf = True
|
self.leaf = True
|
||||||
self.value = MemoryArray(Literal(2 * m - 1))
|
self.value = Array([0] * (2 * m - 1), ctx)
|
||||||
self.children = [None] * (2 * m)
|
self.children = [None] * (2 * m)
|
||||||
self.loaded_count = 0
|
self.loaded_count = 0
|
||||||
self.saved_count = 0
|
self.saved_count = 0
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
super().reset_counters()
|
|
||||||
self.loaded_count = 0
|
|
||||||
self.saved_count = 0
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
self.loaded_count += 1
|
self.loaded_count += 1
|
||||||
|
|
||||||
@ -26,4 +29,4 @@ class BTreeNode(MemoryCell):
|
|||||||
self.saved_count += 1
|
self.saved_count += 1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "(" + " ".join([str(self.value[Literal(i)]) for i in range(self.n)]) + ")"
|
return "(" + " ".join([str(self.value[i]) for i in range(self.n)]) + ")"
|
||||||
|
|||||||
@ -1,58 +1,53 @@
|
|||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
|
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
|
||||||
|
|
||||||
#Goldener Schnitt
|
# Goldener Schnitt (Konstante, nicht instrumentiert)
|
||||||
a = Literal((math.sqrt(5) - 1) / 2)
|
_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 h(x: Int, m: Int) -> Int:
|
||||||
def f(x: MemoryCell, i: Literal, m: Literal) -> Literal:
|
"""Hashfunktion nach multiplikativer Methode."""
|
||||||
c1 = 1
|
full = x.value * _A
|
||||||
c2 = 5
|
return Int(int(abs(full - int(full)) * int(m)), x._ctx)
|
||||||
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
|
|
||||||
|
|
||||||
# Symmetrische quadratische Sondierung
|
|
||||||
def fs(x: MemoryCell, i: Literal, m: Literal) -> Literal:
|
def f(x: Int, i: Int, m: Int) -> Int:
|
||||||
with MemoryCell(h(x, m)) as base_hash, MemoryCell(int(i) * int(i)) as square:
|
"""Quadratische Sondierung."""
|
||||||
if int(i) % 2 == 0: # gerades i: Vorwärtssondierung
|
c1, c2 = 1, 5
|
||||||
with MemoryCell(base_hash + square) as position:
|
base = int(h(x, m))
|
||||||
return position % m
|
probe = base + c1 * int(i) + c2 * int(i) ** 2
|
||||||
else: # ungerades i: Rückwärtssondierung
|
return Int(probe % int(m), x._ctx)
|
||||||
with MemoryCell(base_hash - square) as position:
|
|
||||||
return position % m
|
|
||||||
|
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):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
ht = HashTableOpenAddressing(size, f)
|
ht = HashTableOpenAddressing(size, f, ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
for cell in random_array:
|
for cell in z:
|
||||||
ht.insert(cell)
|
ht.insert(cell)
|
||||||
MemoryManager.reset()
|
ctx.reset()
|
||||||
cell = random.choice(random_array.cells)
|
target = z[random.randint(0, size - 1)]
|
||||||
ht.search(cell)
|
ht.search(target)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,76 +1,75 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import irange
|
||||||
|
|
||||||
|
|
||||||
UNUSED_MARK = "UNUSED"
|
UNUSED_MARK = "UNUSED"
|
||||||
DELETED_MARK = "DELETED"
|
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
|
|
||||||
self.f = f
|
|
||||||
self.table = MemoryArray(m)
|
|
||||||
for i in mrange(m):
|
|
||||||
self.table[i].value = UNUSED_MARK
|
|
||||||
|
|
||||||
def insert(self, x: MemoryCell):
|
class HashTableOpenAddressing:
|
||||||
with MemoryCell(0) as i:
|
"""
|
||||||
|
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 = Array([UNUSED_MARK] * m, ctx)
|
||||||
|
|
||||||
|
def insert(self, x: Int) -> bool:
|
||||||
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_free(j):
|
if self.is_free(j):
|
||||||
self.table[j].set(x)
|
self.table[j] = x
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def search(self, x: MemoryCell):
|
def search(self, x: Int) -> bool:
|
||||||
with MemoryCell(0) as i:
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_unused(j):
|
if self.is_unused(j):
|
||||||
return False
|
return False
|
||||||
if self.table[j] == x:
|
if self.table[j] == x:
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def delete(self, x: MemoryCell):
|
def delete(self, x: Int) -> bool:
|
||||||
with MemoryCell(0) as i:
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_unused(j):
|
if self.is_unused(j):
|
||||||
return False
|
return False
|
||||||
if self.table[j] == x:
|
if self.table[j] == x:
|
||||||
self.table[j].value = DELETED_MARK
|
self.table[j].set(DELETED_MARK) # Tombstone setzen (1 write)
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.table)
|
return str(self.table)
|
||||||
|
|
||||||
def alpha(self):
|
def alpha(self) -> float:
|
||||||
with MemoryCell(0) as i:
|
"""Belegungsfaktor der Tabelle."""
|
||||||
used = 0
|
used = sum(0 if self.is_free(Int(i, self.ctx)) else 1
|
||||||
while i < self.m:
|
for i in range(int(self.m)))
|
||||||
used += 0 if self.is_free(i) else 1
|
|
||||||
i.set(i.succ())
|
|
||||||
return used / int(self.m)
|
return used / int(self.m)
|
||||||
|
|
||||||
def is_unused(self, i: Literal):
|
def is_unused(self, i: Int) -> bool:
|
||||||
if self.table[i].value == UNUSED_MARK:
|
return self.table[i].value == UNUSED_MARK
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_deleted(self, i: Literal):
|
def is_deleted(self, i: Int) -> bool:
|
||||||
if self.table[i].value == DELETED_MARK:
|
return self.table[i].value == DELETED_MARK
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_free(self, i: Literal):
|
def is_free(self, i: Int) -> bool:
|
||||||
return self.is_unused(i) or self.is_deleted(i)
|
return self.is_unused(i) or self.is_deleted(i)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user