From 1853c4d126da04ec427850ef6c198d41c3f99c56 Mon Sep 17 00:00:00 2001 From: schoeffelbe82781 Date: Fri, 11 Apr 2025 22:42:10 +0200 Subject: [PATCH] Implemented Iterative Version of Quicksort to circumvent maxRecDepth error, hunted down reference vs value bugs and implemented timing decorator for comparability --- schoeffelbe/pr02.py | 14 ++++- schoeffelbe/pr03.py | 138 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 120 insertions(+), 32 deletions(-) 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 index 6954c27..cd467b2 100644 --- a/schoeffelbe/pr03.py +++ b/schoeffelbe/pr03.py @@ -1,6 +1,17 @@ import logging logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.DEBUG) +# 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 @@ -13,8 +24,7 @@ def example(): initial = [6, 5, 3, 8, 1, 7, 2, 4] # initial = [-6, -5, -3, -8, 1, 7, 2, 4] toSort = MemoryArray(initial) - # init_from_size not accessible? - quickSort(toSort, Literal(0), mode=0) + 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" @@ -24,7 +34,9 @@ def getPivot(z: MemoryArray, l: Literal, r: Literal, mode) -> Literal: if mode == 0: return r else: - mid = MemoryCell(l) + MemoryCell(MemoryCell(r) - l) // Literal(2) + 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 @@ -42,15 +54,80 @@ def swap(z: MemoryArray, i: int, j: int): z[Literal(i)] = z[Literal(j)] z[Literal(j)].set(tmp) -def quickSort(z: MemoryArray, l: Literal = Literal(0), r: Literal = Literal(-1), mode=0): + +# 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 = partition(z, l, r, mode) - quickSort(z, l, q.pred()) - quickSort(z, q.succ(), r) + q = LEGACY_partition(z, l, r, mode) + LEGACY_quickSort(z, l, q.pred()) + LEGACY_quickSort(z, q.succ(), r) -def partition(z: MemoryArray, l: Literal, r: Literal, mode): +def LEGACY_partition(z: MemoryArray, l: Literal, r: Literal, mode): # Get pivot pivot_idx = getPivot(z, l, r, mode) @@ -58,25 +135,21 @@ def partition(z: MemoryArray, l: Literal, r: Literal, mode): 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: - pivot = MemoryCell(z[r]) - i = MemoryCell(l) - j = MemoryCell(r.pred()) - - while i < j: - while z[i] < pivot: + 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()) - 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) + if z[i] != pivot: + swap(z, int(i), int(r)) + return Literal(i) def analyze_complexity(fn, sizes): @@ -100,12 +173,17 @@ 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) - quickSort(toSort) + quickSortIterative(toSort, Literal(0), toSort.length().pred()) print(toSort) - for filename in ["data/seq0.txt", "data/seq1.txt", "data/seq2.txt", "data/seq3.txt"]: + 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) - quickSort(toSort,Literal(0), Literal(-1), mode=0) + timeMS(quickSortIterative, toSort, Literal(0), toSort.length().pred(), mode=1) + # print(toSort)