forked from hofmannol/AlgoDatSoSe25
335 lines
11 KiB
Python
335 lines
11 KiB
Python
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())
|
|
|