diff --git a/schoeffelbe/pr02.py b/schoeffelbe/pr02.py index 8b65f0a..d26bfae 100644 --- a/schoeffelbe/pr02.py +++ b/schoeffelbe/pr02.py @@ -2,6 +2,16 @@ import logging 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 @@ -111,5 +121,5 @@ if __name__ == '__main__': print(filename) toSort = MemoryArray.create_array_from_file(filename) sorted = MemoryArray([-1] * toSort.length().get()) - mergeSort(toSort, sorted) - print(sorted) + timeMS(mergeSort, toSort, sorted) + # print(sorted) diff --git a/schoeffelbe/pr03.py b/schoeffelbe/pr03.py new file mode 100644 index 0000000..35f390d --- /dev/null +++ b/schoeffelbe/pr03.py @@ -0,0 +1,190 @@ +import logging +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.constants import MIN_VALUE +from utils.memory_manager import MemoryManager +from utils.memory_range import mrange + +def example(): + initial = [6, 5, 3, 8, 1, 7, 2, 4] + # initial = [-6, -5, -3, -8, 1, 7, 2, 4] + toSort = MemoryArray(initial) + quickSortIterative(toSort, Literal(0), toSort.length().pred()) + logger.debug(f"sorted {toSort} vs initial {initial}") + assert all(toSort[Literal(i)] == Literal(i+1) for i in range(len(initial))), "Array not sorted correctly" + + # analyze_complexity(quickSort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) + +def getPivot(z: MemoryArray, l: Literal, r: Literal, mode) -> Literal: + if mode == 0: + return r + else: + mid_offset = r.value - l.value + mid_offset = mid_offset // 2 + mid = Literal(l.value + mid_offset) + + # Return median of left, middle, and right elements + if ((z[l] <= z[mid] and z[mid] <= z[r]) or + (z[r] <= z[mid] and z[mid] <= z[l])): + return mid + elif ((z[mid] <= z[l] and z[l] <= z[r]) or + (z[r] <= z[l] and z[l] <= z[mid])): + return l + else: + return r + + +def swap(z: MemoryArray, i: int, j: int): + tmp = z[Literal(i)].value + z[Literal(i)] = z[Literal(j)] + z[Literal(j)].set(tmp) + +# toSort[] --> Array to be sorted, +# left --> Starting index, +# right --> Ending index +# adapted from https://stackoverflow.com/questions/68524038/is-there-a-python-implementation-of-quicksort-without-recursion +def quickSortIterative(toSort : MemoryArray, left : Literal, right : Literal, mode=0): + # Create a manually managed stack and avoid pythons recursion-limit + size = right.value - left.value + 1 + stack : MemoryArray = MemoryArray([0] * size) + top : MemoryCell = MemoryCell(-1) + + # push initial values of l and h to stack + top += Literal(1) + stack[top] = left + top += Literal(1) + stack[top] = right + + # Keep popping from stack until its empty + while top >= Literal(0): + logger.debug(f"size {size}, stack {stack}, right {right} and left {left}, top {top}") + + # Pop h and l - Ensure we are not getting them by Ref, this will produce weird "JUST A LITTLE OF" Results + right = Literal(stack[top].get()) + top -= Literal(1) + left = Literal(stack[top].get()) + top -= Literal(1) + + # Set pivot element at its correct position in sorted array + p = partitionIterative(toSort, left, right, mode) + + # If there are elements on left side of pivot, then push left side to stack + if p.pred() > left: + top += Literal(1) + stack[top] = left + top += Literal(1) + stack[top] = p.pred() + + # If there are elements on right side of pivot, then push right side to stack + if p.succ() < right: + top += Literal(1) + stack[top] = p.succ() + top += Literal(1) + stack[top] = right + +def partitionIterative(arr : MemoryArray, l : Literal, h : Literal, mode=0): + logger.debug(f"Partitioning {arr}, {l} and {h}") + pivot_idx : Literal = getPivot(arr, l, h, mode) + + # If pivot isn't at the high end, swap it there + if pivot_idx != h: + swap(arr, int(pivot_idx), int(h)) + + # Carefull that we do not use a reference. I suppose python would return one here if we just assign without value>Literal cast. + # At least this helped fix weird issue + pivotValue : Literal = Literal(arr[h].value) + i : MemoryCell = MemoryCell(l.pred()) + + for j in mrange(l, h): + if arr[j] <= pivotValue: + i += Literal(1) # increment index of smaller element + swap(arr, int(i), int(j)) + + swap(arr, int(i.succ()), int(h)) + return i.succ() + +def LEGACY_quickSort(z: MemoryArray, l: Literal = Literal(0), r: Literal = Literal(-1), mode=0): + if r == Literal(-1): + r = z.length().pred(); + if l < r: + q = LEGACY_partition(z, l, r, mode) + LEGACY_quickSort(z, l, q.pred()) + LEGACY_quickSort(z, q.succ(), r) + +def LEGACY_partition(z: MemoryArray, l: Literal, r: Literal, mode): + # Get pivot + pivot_idx = getPivot(z, l, r, mode) + + # If pivot is not already at the right end, swap it there + if pivot_idx != r: + swap(z, int(pivot_idx), int(r)) + + with MemoryCell(z[r]) as pivot, MemoryCell(l) as i, MemoryCell(r.pred()) as j: + while i < j: + 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) + + +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) + fn(random_array, Literal(0), random_array.length().pred()) + MemoryManager.save_stats(size) + + MemoryManager.plot_stats(["cells", "adds", "compares", "reads", "writes"]) + + +if __name__ == '__main__': + # For debug, assert if working and complexity-analysis + example() + + print("I ran into a MaxRecursionDepth Error. From what I read on the Internet python does not do Tailcall Optimizations") + print("Increasing recursion-limit seems like a poor Idea, therefore tried an iterative approach with manual stack-keeping") + + toSort = MemoryArray.create_array_from_file("data/seq0.txt") + print(toSort) + quickSortIterative(toSort, Literal(0), toSort.length().pred()) + print(toSort) + + # analyze_complexity(quickSortIterative, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) + for filename in ["data/seq0.txt", "data/seq1.txt", "data/seq2.txt" ,"data/seq3.txt"]: + # for filename in [ "data/seq1.txt"]: + print(filename) + toSort = MemoryArray.create_array_from_file(filename) + timeMS(quickSortIterative, toSort, Literal(0), toSort.length().pred(), mode=1) + # print(toSort) + + print("Kann durch die Modifikation eine besser Laufzeit als nlog(n) erreicht werden? Nein! nlog(n) ist das Minimum. Durch die Änderung kann aber der Worst-Case fall von n^2 für z.B. bereits vorsortierte Arrays oder Arrays mit vielen Duplikaten vermieden werden.") diff --git a/schoeffelbe/priorityQueue.py b/schoeffelbe/priorityQueue.py new file mode 100644 index 0000000..c5fb212 --- /dev/null +++ b/schoeffelbe/priorityQueue.py @@ -0,0 +1,158 @@ +from utils.memory_array import MemoryArray +from utils.memory_cell import MemoryCell +from utils.literal import Literal +from utils.constants import MAX_VALUE +from utils.memory_range import mrange + +# Impl of MemoryArray says we cant add our own Datatypes beside Literal and List +# BUUUUT we can just wrap our Datatype in a List :-) +# We store them in a MemoryArray internaly tho anyhow so we increment our Counters for the RAM +class HeapEntry: + def __init__(self, item, priority=1): + self.data = MemoryArray(Literal(2)) + # 0: Content, 1: Prio + self.data[Literal(0)] = Literal(item) + self.data[Literal(1)] = Literal(priority) + + def getItem(self): + return self.data[Literal(0)] + + def getPriority(self): + return self.data[Literal(1)] + + def setPriority(self, priority): + self.data[Literal(1)] = Literal(priority) + + def __lt__(self, other): + if other is None: + return True + if isinstance(other, (int, float)): + return self.getPriority().value > other + return self.getPriority() > other.getPriority() + + def __gt__(self, other): + if other is None: + return False + if isinstance(other, (int, float)): + return self.getPriority().value < other + return self.getPriority() < other.getPriority() + + def __eq__(self, other): + return self.getPriority() == other.getPriority() + + def __str__(self): + return f"({self.getItem()}, prio={self.getPriority()})" + +class PriorityQueue: + def __init__(self, max_size : Literal = Literal(100)): + self.heap = MemoryArray(max_size) + # Add uninitialized HeapEntry Values so the Adds/Compares do not fail on emtpy stack. + # Would have to switch to MIN_VALUE if we switch what is a "Higher" Prio + for i in mrange(max_size.value): + self.heap[i].set([HeapEntry(MAX_VALUE, MAX_VALUE)]) + self.size = MemoryCell(0) + + def parent(self, i: Literal) -> Literal: + return MemoryCell(i.pred()) // Literal(2) + + def leftChild(self, i: Literal) -> Literal: + return MemoryCell(MemoryCell(2) * i) + Literal(1) + + def rightChild(self, i: Literal) -> Literal: + return MemoryCell(MemoryCell(2) * i) + Literal(2) + + # Swap the Lists -> Therefore get the value which is the List and then Set it again + def swap(self, i: Literal, j: Literal): + tmp_i = self.heap[i].value + tmp_j = self.heap[j].value + self.heap[i].set(tmp_j) + self.heap[j].set(tmp_i) + + def maxHeapify(self, i: Literal): + left = self.leftChild(i) + right = self.rightChild(i) + largest = i + + if left < Literal(self.size.value) and self.heap[left].value[0] > self.heap[largest].value[0]: + largest = left + + if right < Literal(self.size.value) and self.heap[right].value[0] > self.heap[largest].value[0]: + largest = right + + if largest != i: + self.swap(i, largest) + self.maxHeapify(largest) + + def insert(self, entry : HeapEntry): + if self.size >= self.heap.length(): + raise IndexError("Heap full") + + i = self.size + self.heap[i].set([entry]) + self.size += Literal(1) + + while i > Literal(0) and self.heap[self.parent(i)].value[0] < self.heap[i].value[0]: + self.swap(i, self.parent(i)) + i = self.parent(i) + + def pop(self): + if self.isEmpty(): + raise IndexError("Queue is empty!") + + max_item = self.heap[Literal(0)].value[0] + + self.heap[Literal(0)] = self.heap[self.size - Literal(1)] + self.size -= Literal(1) + + self.maxHeapify(Literal(0)) + + return max_item + + def peek(self): + if self.isEmpty(): + raise IndexError("Queue is empty") + return self.heap[Literal(0)].value[0] + + def isEmpty(self): + return self.size == Literal(0) + + def __len__(self): + return self.size + + +if __name__ == '__main__': + # Proof of Concept + testEntry = HeapEntry("A", 2) + print(testEntry) + testArray = MemoryArray([testEntry]) + print(testArray) + print(testArray[Literal(0)]) + + # Queue Testing + pq = PriorityQueue() + try: + pq.pop() + assert False, "Queue should be empty" + except IndexError: + pass + assert(pq.isEmpty() and pq.size == Literal(0)) + entry = HeapEntry("A", 1) + pq.insert(entry) + assert(not pq.isEmpty() and pq.size == Literal(1)) + pq.peek() + assert(not pq.isEmpty()) + assert(pq.pop() == HeapEntry("A", 1)) + assert(pq.isEmpty()) + pq.insert(HeapEntry("A", 1)) + pq.insert(HeapEntry("C", 3)) + pq.insert(HeapEntry("B", 2)) + assert(pq.size == Literal(3)) + assert(pq.pop() == HeapEntry("A", 1)) + assert(pq.pop() == HeapEntry("B", 2)) + assert(pq.pop() == HeapEntry("C", 3)) + pq.insert(HeapEntry("A", 1)) + pq.insert(HeapEntry("C", 3)) + pq.insert(HeapEntry("B", 2)) + print(pq.pop()) + print(pq.pop()) + print(pq.pop())