Squash merge be/pr06 into main

This commit is contained in:
Bernhard Schoeffel 2025-05-05 22:09:09 +02:00
parent 02d9557e27
commit 1a2f826a06

334
schoeffelbe/pr06.py Normal file
View File

@ -0,0 +1,334 @@
import logging
from graphviz import Digraph
logger = logging.getLogger(__name__)
# logging.basicConfig(level=logging.DEBUG)
import time
def timeMS(func, *args, **kwargs):
startTime = time.perf_counter()
result = func(*args, **kwargs)
endTime = time.perf_counter()
elapsedMS = (endTime - startTime) * 1000 # Convert to milliseconds
print(f"{func.__name__} took {elapsedMS:.2f} ms")
return result
from utils.memory_array import MemoryArray
from utils.memory_cell import MemoryCell
from utils.literal import Literal
from utils.memory_range import mrange
from utils.memory_manager import MemoryManager
# We often want to "dynamically" extend our array, but thats not really what it was intended for
# Therefore we write a function to do so reusably
def memArrayInsert(array: MemoryArray, position: Literal, value) -> MemoryArray:
logger.debug(f"Pre-Insert: {array}")
newSize : Literal = array.length().succ()
newArray = MemoryArray(newSize)
# Copy elements til position
for i in mrange(position):
newArray[i] = array[i]
# Insert new value with check for MemCell Type
newArray[position] = value if isinstance(value, MemoryCell) else MemoryCell(value)
for i in mrange(position.succ(), newSize):
newArray[i] = array[i.pred()]
logger.debug(f"Post-Insert: {array}")
return newArray
# Likewise for the Split
# Here we split the array at the position EXCLUSIVELY for the first MemoryArray
def memArraySplit(array: MemoryArray, position: Literal) -> tuple[MemoryArray, MemoryArray]:
leftSize = position
rightSize = MemoryCell(array.length()) - position
left = MemoryArray(leftSize)
right = MemoryArray(rightSize)
for i in mrange(leftSize):
left[i] = array[i]
for i in mrange(rightSize):
right[i] = array[Literal(i.value + position.value)]
return left, right
class BTreeNode(MemoryCell):
def __init__(self):
super().__init__(value=None)
self.value = MemoryArray([])
self.children = MemoryArray([])
self.leaf = True
def __str__(self):
return "( " + " ".join(str(val) for val in self.value) + " )"
def isLeaf(self):
return self.children.length() == Literal(0)
class BTree:
def __init__(self, order):
self.order = Literal(order)
self.root = BTreeNode()
def _insertNonFull(self, node, key):
if node.leaf:
i = node.value.length()
# pos for new Key
while i > Literal(0) and key < node.value[i.pred()]:
i = i.pred()
# insert key at pos using helper fkt
node.value = memArrayInsert(node.value, i, key)
else:
j = Literal(0)
while j < node.value.length() and node.value[j] < key:
j = j.succ()
child = node.children[j].value[0]
# Splt if full
if child.value.length() == Literal(2 * self.order.value - 1):
self._splitChild(node, j)
if key > node.value[j]:
j = j.succ()
child = node.children[j].value[0]
# insert rec in selected Child
self._insertNonFull(child, key)
def insert(self, key):
if not isinstance(key, MemoryCell):
key = MemoryCell(key)
rootRef = self.root
if rootRef.value.length() == MemoryCell(2 * self.order.value - 1):
newRoot = BTreeNode()
newRoot.leaf = False
newRoot.children = MemoryArray(["DUMMY"])
newRoot.children[Literal(0)].set([rootRef])
self._splitChild(newRoot, Literal(0))
self.root = newRoot
self._insertNonFull(newRoot, key)
else:
logger.debug("Inserting in non-full");
self._insertNonFull(rootRef, key)
def _splitChild(self, parent: BTreeNode, index: Literal):
child = parent.children[index].value[0]
newRight = BTreeNode()
newRight.leaf = child.leaf
# median index
mid = Literal(self.order.value - 1)
medianKey = child.value[mid]
# Childs keys in l and r
leftKeys, rightKeys = memArraySplit(child.value, mid)
child.value = leftKeys
newRight.value = MemoryArray(rightKeys.length().pred())
for j in mrange(rightKeys.length().pred()):
newRight.value[j] = rightKeys[j.succ()]
# if child is not leaf distribute itschildren
if not child.leaf:
leftChildren, rightChildren = memArraySplit(child.children, mid.succ())
child.children = leftChildren
newRight.children = rightChildren
# Insert median key in parent and insert new right as i+1's chld
parent.value = memArrayInsert(parent.value, index, medianKey)
parent.children = memArrayInsert(parent.children, index.succ(), [newRight])
def search(self, key) -> BTreeNode:
return BTreeNode()
# Depth-Search
def _traversalInOrder(self, node : BTreeNode, result):
logger.debug(type(node.value))
logger.debug(node.value)
for i in mrange(node.value.length()):
# RecTerm
if not node.isLeaf():
self._traversalInOrder(node.children[i].value[0], result)
result.append(str(node.value[i]))
# RecTerm
if not node.isLeaf():
self._traversalInOrder(node.children[Literal(len(node.value))].value[0], result)
def structureTraversal(self, callback):
def traverse(node):
if node is None:
return
callback(node)
if not node.isLeaf():
for i in range(len(node.children)):
child = node.children[Literal(i)].value[0]
traverse(child)
traverse(self.root)
def traversal(self) -> list:
resultAcc = []
self._traversalInOrder(self.root, resultAcc)
return resultAcc
def search(self, searchVal : Literal) -> BTreeNode|None:
if self.root is None:
return None
current = self.root
while not current.isLeaf():
# Exit early if we are already way to large for our current Node. Should improve runtime
if searchVal > current.value[current.value.length().pred()]:
current = current.children[current.value.length()].value[0]
continue
# Go thru every value in the Node until we find anything
i = MemoryCell(0)
while i < current.value.length() and searchVal > current.value[i]:
i = i.succ()
# return exact match
if i < current.value.length() and searchVal == current.value[i]:
return current
# go to appropriate child
# If searchVal smaller than first value (i=0) -> leftmost child
# if searchVal larger than all values (i=len) -> rightmost child
# Otherwise -> child between values
current = current.children[Literal(i)].value[0]
# Final check in leaf node
for i in mrange(current.value.length()):
if searchVal == current.value[i]:
return current
return None
def height(self) -> Literal:
if self.root is None:
return Literal(0)
current = self.root
height = MemoryCell(1)
while not current.isLeaf():
height = height.succ()
current = current.children[Literal(0)].value[0]
return height
def clbk_graphvizify(toDecorate: BTreeNode, indent=0, line=None):
global dotAcc
values_str = "|".join(str(val) for val in toDecorate.value)
dotAcc.node(f'n_{id(toDecorate)}', values_str, shape='record')
# Create edges for all children
if not toDecorate.isLeaf():
for i in mrange(toDecorate.children.length()):
child = toDecorate.children[i].value[0]
dotAcc.edge(f'n_{id(toDecorate)}', f'n_{id(child)}')
def visualizeBTree(tree: BTree, filename='build/schoeffel_btree'):
try:
import graphviz
except ImportError:
raise AssertionError("Graphviz installed? Try commenting visualizeBTree or install 'pip install graphviz'")
global dotAcc
dotAcc = Digraph()
dotAcc.attr(rankdir='TB')
dotAcc.attr('node', shape='record', style='filled', fillcolor='lightgray')
dotAcc.attr('edge', arrowsize='0.5')
tree.structureTraversal(clbk_graphvizify)
try:
dotAcc.render(filename, view=True)
except Exception as e:
print(f"Could not display graph: {e}")
print("Saving graph file without viewing (Running WSL?)")
try:
dotAcc.render(filename, view=False)
except Exception as e:
print(f"Could not save graph file: {e}")
def graphvizify() -> str:
result = """digraph {
rankdir=TB;
node [shape=record, style=filled, fillcolor=lightgray];
edge [arrowsize=0.5];
"""
# Body
result += '\n\t'.join(str(item) for item in dotAcc)
# Footer
result += "\n}"
return result
def analyze_complexity(fn, sizes):
"""
Analysiert die Komplexität einer maximalen Teilfolgenfunktion.
:param max_sequence_func: Die Funktion, die analysiert wird.
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
"""
for size in sizes:
MemoryManager.purge() # Speicher zurücksetzen
random_array = MemoryArray.create_random_array(size, -100, 100)
for value in random_array:
fn(value)
MemoryManager.save_stats(size)
MemoryManager.plot_stats(["cells", "compares", "reads", "writes"])
if __name__ == '__main__':
tree = BTree(order=3)
tree.insert(Literal(10));
tree.insert(Literal(11));
tree.insert(Literal(12));
print(tree.traversal());
binTreeData = MemoryArray.create_array_from_file("data/seq0.txt")
j = 0
for value in binTreeData:
tree.insert(value)
j = j +1
# Uncomment to view progress in insertion at every step saved as PDF
# visualizeBTree(tree, f"build/schoeffel_btree{j}")
logger.debug(tree.root.children)
# Graphvizify Wrapper
visualizeBTree(tree)
print(f"InOrder Traversal: {tree.traversal()}")
print(tree.search(Literal(50)))
print(tree.height())
tree3 = BTree(order=3)
tree5 = BTree(order=5)
binTreeData = MemoryArray.create_array_from_file("data/seq2.txt")
for value in binTreeData:
tree3.insert(value)
tree5.insert(value)
print(f"Order three for Seq2 produces height {tree3.height()}")
print(f"Order five for Seq2 produces height {tree5.height()}")
order3 = tree3.traversal()
order5 = tree5.traversal()
assert all(int(order3[i]) <= int(order3[i+1]) for i in range(len(order3)-1)), "Order3 not in ascending order"
assert all(int(order5[i]) <= int(order5[i+1]) for i in range(len(order5)-1)), "Order5 not in ascending order"
print(tree3.search(Literal(0)))
print(tree5.search(Literal(0)))
MemoryManager.purge()
analyze_complexity(tree3.insert, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
# tree.tree_structure_traversal(clbk_graphvizify)
# print(graphvizify())