From 1a2f826a06b7b13945a5e6ca51d912aa05bf45fc Mon Sep 17 00:00:00 2001 From: schoeffelbe82781 Date: Mon, 5 May 2025 22:09:09 +0200 Subject: [PATCH] Squash merge be/pr06 into main --- schoeffelbe/pr06.py | 334 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 schoeffelbe/pr06.py diff --git a/schoeffelbe/pr06.py b/schoeffelbe/pr06.py new file mode 100644 index 0000000..4f91369 --- /dev/null +++ b/schoeffelbe/pr06.py @@ -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()) +