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 = arr[h] 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, i.succ().value, h.value) 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.")