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())