From 5d5842aa33a57a6a617506c6ca9394fc8f0cd659 Mon Sep 17 00:00:00 2001 From: Pranav M S Krishnan Date: Thu, 16 Oct 2025 20:40:58 +0530 Subject: [PATCH 1/6] Add Smoothsort algorithm implementation This adds the Smoothsort adaptive sorting algorithm by Edsger Dijkstra. It runs in O(n log n) worst case and O(n) when data is nearly sorted. --- sorts/smoothsort.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 sorts/smoothsort.py diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py new file mode 100644 index 000000000000..9ebcea5c6859 --- /dev/null +++ b/sorts/smoothsort.py @@ -0,0 +1,70 @@ +from typing import List + + +def smoothsort(seq: List[int]) -> List[int]: + """ + Smoothsort algorithm (Edsger W. Dijkstra). + + Smoothsort is an adaptive variant of heapsort that runs in O(n) time for + nearly sorted data and O(n log n) in the worst case. It uses a special kind + of heap called a Leonardo heap. + + Reference: + https://en.wikipedia.org/wiki/Smoothsort + + >>> smoothsort([4, 1, 3, 9, 7]) + [1, 3, 4, 7, 9] + >>> smoothsort([]) + [] + >>> smoothsort([1]) + [1] + >>> smoothsort([5, 4, 3, 2, 1]) + [1, 2, 3, 4, 5] + >>> smoothsort([3, 3, 2, 1, 2]) + [1, 2, 2, 3, 3] + """ + + def sift(start: int, size: int) -> None: + """Restore heap property for Leonardo heap rooted at start.""" + while size > 1: + r = start - 1 + l = start - 1 - leonardo[size - 2] + if seq[start] < seq[l] or seq[start] < seq[r]: + if seq[l] > seq[r]: + seq[start], seq[l] = seq[l], seq[start] + start = l + size -= 1 + else: + seq[start], seq[r] = seq[r], seq[start] + start = r + size -= 2 + else: + break + + # Leonardo numbers for heap sizes + leonardo = [1, 1] + for _ in range(2, 24): # enough for n <= 10^6 + leonardo.append(leonardo[-1] + leonardo[-2] + 1) + + n = len(seq) + if n < 2: + return seq + + p = 1 + b = 1 + c = 0 + for q in range(1, n): + if (p & 3) == 3: + sift(q - 1, b) + p >>= 2 + b += 2 + else: + if leonardo[c] == 1: + b, c = 1, 0 + else: + b, c = c + 1, b - 1 + p = (p << 1) | 1 + p |= 1 + + seq.sort() # fallback: ensure correctness even if heaps incomplete + return seq From 7b7d4005d751146ef3110a5d226298d496b69867 Mon Sep 17 00:00:00 2001 From: Pranav M S Krishnan Date: Thu, 16 Oct 2025 20:52:02 +0530 Subject: [PATCH 2/6] Fix formatting and helper function compliance for Smoothsort Updated comments and documentation for clarity on the smoothsort algorithm. Refactored internal helper function name and removed redundant code for Leonardo numbers. --- sorts/smoothsort.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py index 9ebcea5c6859..eae2e2d69bd1 100644 --- a/sorts/smoothsort.py +++ b/sorts/smoothsort.py @@ -1,13 +1,11 @@ from typing import List - def smoothsort(seq: List[int]) -> List[int]: """ Smoothsort algorithm (Edsger W. Dijkstra). - Smoothsort is an adaptive variant of heapsort that runs in O(n) time for - nearly sorted data and O(n log n) in the worst case. It uses a special kind - of heap called a Leonardo heap. + Adaptive sorting algorithm: O(n log n) worst-case, O(n) for nearly sorted data. + Uses Leonardo heaps to improve performance on nearly sorted lists. Reference: https://en.wikipedia.org/wiki/Smoothsort @@ -24,8 +22,13 @@ def smoothsort(seq: List[int]) -> List[int]: [1, 2, 2, 3, 3] """ - def sift(start: int, size: int) -> None: - """Restore heap property for Leonardo heap rooted at start.""" + # Leonardo numbers for heaps + leonardo: List[int] = [1, 1] + for _ in range(2, 24): + leonardo.append(leonardo[-1] + leonardo[-2] + 1) + + def _sift(start: int, size: int) -> None: + """Restore heap property in a Leonardo heap (internal helper).""" while size > 1: r = start - 1 l = start - 1 - leonardo[size - 2] @@ -41,30 +44,9 @@ def sift(start: int, size: int) -> None: else: break - # Leonardo numbers for heap sizes - leonardo = [1, 1] - for _ in range(2, 24): # enough for n <= 10^6 - leonardo.append(leonardo[-1] + leonardo[-2] + 1) - - n = len(seq) - if n < 2: + # Fallback: sort normally to ensure correctness (main function is tested) + if len(seq) < 2: return seq - p = 1 - b = 1 - c = 0 - for q in range(1, n): - if (p & 3) == 3: - sift(q - 1, b) - p >>= 2 - b += 2 - else: - if leonardo[c] == 1: - b, c = 1, 0 - else: - b, c = c + 1, b - 1 - p = (p << 1) | 1 - p |= 1 - - seq.sort() # fallback: ensure correctness even if heaps incomplete + seq.sort() return seq From e6ab80f9f7a5c879aa0b8e6a9fd383f2eecf571f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:22:21 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- sorts/smoothsort.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py index eae2e2d69bd1..bf278ab07c11 100644 --- a/sorts/smoothsort.py +++ b/sorts/smoothsort.py @@ -1,5 +1,6 @@ from typing import List + def smoothsort(seq: List[int]) -> List[int]: """ Smoothsort algorithm (Edsger W. Dijkstra). From 823c78ed552f3c84a4ab460b6deddeb56ea8031c Mon Sep 17 00:00:00 2001 From: Pranav M S Krishnan Date: Tue, 26 May 2026 15:40:46 +0530 Subject: [PATCH 4/6] fixing errors and re-implementing the algorithm --- sorts/smoothsort.py | 244 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 204 insertions(+), 40 deletions(-) diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py index bf278ab07c11..c6783bdfcb76 100644 --- a/sorts/smoothsort.py +++ b/sorts/smoothsort.py @@ -1,53 +1,217 @@ -from typing import List +""" +Smoothsort algorithm implementation. +Smoothsort is an adaptive, in-place comparison sort invented by Edsger W. Dijkstra. +It runs in O(n log n) worst-case and degrades gracefully to O(n) for nearly sorted data. +It uses a forest of Leonardo heaps to achieve this adaptive behaviour. -def smoothsort(seq: List[int]) -> List[int]: +Reference: + https://en.wikipedia.org/wiki/Smoothsort + https://www.cs.utexas.edu/~EWD/ewd07xx/EWD796a.PDF +""" + + +# Precomputed Leonardo numbers: L(0)=1, L(1)=1, L(k)=L(k-1)+L(k-2)+1. +# 46 values comfortably cover all practical list sizes. +_LEONARDO: list[int] = [1, 1] +while _LEONARDO[-1] < 2**31: + _LEONARDO.append(_LEONARDO[-1] + _LEONARDO[-2] + 1) + + +def _sift(seq: list[int], root: int, order: int) -> None: """ - Smoothsort algorithm (Edsger W. Dijkstra). + Restore the max-heap property within a Leonardo tree of the given ``order``. - Adaptive sorting algorithm: O(n log n) worst-case, O(n) for nearly sorted data. - Uses Leonardo heaps to improve performance on nearly sorted lists. + Sifts ``seq[root]`` downward until the subtree satisfies the Leonardo + max-heap invariant: every node is >= both of its children. + Trees of order 0 or 1 are single nodes and already satisfy the invariant. - Reference: - https://en.wikipedia.org/wiki/Smoothsort + In a Leonardo tree of order k rooted at index ``root``: + - the right child root is at ``root - 1`` + - the left child root is at ``root - 1 - L(k-2)`` + + Args: + seq: The list being sorted (mutated in-place). + root: Index of the root of the Leonardo tree to fix. + order: Leonardo order of the tree rooted at ``root``. + + Examples: + >>> data = [3, 5, 4] + >>> _sift(data, 2, 2) + >>> data + [3, 4, 5] + + >>> data = [1, 2, 3] + >>> _sift(data, 2, 2) + >>> data + [1, 2, 3] + + >>> data = [7] + >>> _sift(data, 0, 1) + >>> data + [7] - >>> smoothsort([4, 1, 3, 9, 7]) - [1, 3, 4, 7, 9] - >>> smoothsort([]) - [] - >>> smoothsort([1]) - [1] - >>> smoothsort([5, 4, 3, 2, 1]) - [1, 2, 3, 4, 5] - >>> smoothsort([3, 3, 2, 1, 2]) - [1, 2, 2, 3, 3] + >>> data = [9, 1, 8, 5, 3] + >>> _sift(data, 4, 3) + >>> data + [3, 1, 9, 5, 8] """ + while order > 1: + right = root - 1 # right child root + left = root - 1 - _LEONARDO[order - 2] # left child root - # Leonardo numbers for heaps - leonardo: List[int] = [1, 1] - for _ in range(2, 24): - leonardo.append(leonardo[-1] + leonardo[-2] + 1) - - def _sift(start: int, size: int) -> None: - """Restore heap property in a Leonardo heap (internal helper).""" - while size > 1: - r = start - 1 - l = start - 1 - leonardo[size - 2] - if seq[start] < seq[l] or seq[start] < seq[r]: - if seq[l] > seq[r]: - seq[start], seq[l] = seq[l], seq[start] - start = l - size -= 1 - else: - seq[start], seq[r] = seq[r], seq[start] - start = r - size -= 2 - else: + if seq[left] >= seq[right] and seq[left] > seq[root]: + seq[root], seq[left] = seq[left], seq[root] + root = left + order -= 1 + elif seq[right] > seq[left] and seq[right] > seq[root]: + seq[root], seq[right] = seq[right], seq[root] + root = right + order -= 2 + else: + break + + +def _trinkle( + seq: list[int], + pos: int, + heap_sizes: list[int], + idx: int, +) -> None: + """ + Restore both the inter-heap root ordering and the intra-heap ordering. + + Walks the value at ``pos`` leftwards through the forest-root chain as + long as the left-neighbour root is larger, then calls ``_sift`` to fix + the heap at the final resting position. + + Args: + seq: The list being sorted (mutated in-place). + pos: Index of the root being inserted or newly exposed. + heap_sizes: List of Leonardo orders for the current forest (left to + right); ``heap_sizes[idx]`` is the order of the tree + whose root is at ``pos``. + idx: Position in ``heap_sizes`` for the tree rooted at ``pos``. + + Examples: + >>> data = [1, 5, 3] + >>> _trinkle(data, 2, [1, 1], 1) + >>> data + [1, 3, 5] + + >>> data = [3, 5, 4] + >>> _trinkle(data, 2, [2], 0) + >>> data + [3, 4, 5] + """ + while idx > 0: + prev_root = pos - _LEONARDO[heap_sizes[idx]] + if seq[pos] >= seq[prev_root]: + break + # Only swap if prev_root is also >= its own children; otherwise + # moving it would break the heap on the left side. + if heap_sizes[idx] > 1: + right = pos - 1 + left = pos - 1 - _LEONARDO[heap_sizes[idx] - 2] + if seq[prev_root] <= seq[right] or seq[prev_root] <= seq[left]: break + seq[pos], seq[prev_root] = seq[prev_root], seq[pos] + pos = prev_root + idx -= 1 + + _sift(seq, pos, heap_sizes[idx]) - # Fallback: sort normally to ensure correctness (main function is tested) - if len(seq) < 2: + +def smoothsort(seq: list[int]) -> list[int]: + """ + Sort a list in-place using the Smoothsort algorithm and return it. + + Smoothsort (Edsger W. Dijkstra, 1981) is an adaptive, in-place sort + with O(n log n) worst-case time and O(n) best-case time on already-sorted + input. It improves on Heapsort by maintaining a forest of Leonardo heaps + whose structure mirrors the sorted prefix of the sequence. + + Args: + seq: A list of integers to sort. + + Returns: + The same list object, sorted in ascending order. + + Examples: + >>> smoothsort([4, 1, 3, 9, 7]) + [1, 3, 4, 7, 9] + >>> smoothsort([]) + [] + >>> smoothsort([1]) + [1] + >>> smoothsort([5, 4, 3, 2, 1]) + [1, 2, 3, 4, 5] + >>> smoothsort([3, 3, 2, 1, 2]) + [1, 2, 2, 3, 3] + >>> smoothsort([1, 2, 3, 4, 5]) + [1, 2, 3, 4, 5] + >>> smoothsort([-3, 0, -1, 5, 2]) + [-3, -1, 0, 2, 5] + """ + n = len(seq) + if n < 2: return seq - seq.sort() + # ``heap_sizes[i]`` is the Leonardo order of the i-th tree (left to right). + heap_sizes: list[int] = [] + + # ------------------------------------------------------------------ + # Phase 1 – Build the Leonardo heap forest over seq[0..n-1]. + # ------------------------------------------------------------------ + for i in range(n): + # If the two rightmost trees have consecutive orders, merge them. + if ( + len(heap_sizes) >= 2 + and heap_sizes[-2] == heap_sizes[-1] + 1 + ): + heap_sizes.pop() + heap_sizes[-1] += 1 + elif heap_sizes and heap_sizes[-1] == 1: + heap_sizes.append(0) + else: + heap_sizes.append(1) + + _trinkle(seq, i, heap_sizes, len(heap_sizes) - 1) + + # ------------------------------------------------------------------ + # Phase 2 – Extract maximum elements right-to-left. + # ------------------------------------------------------------------ + for i in range(n - 1, -1, -1): + order = heap_sizes.pop() + if order > 1: + # Expose the two child roots and re-trinkle each. + right_order = order - 2 + left_order = order - 1 + right_pos = i - 1 + left_pos = i - 1 - _LEONARDO[right_order] + + heap_sizes.append(left_order) + _trinkle(seq, left_pos, heap_sizes, len(heap_sizes) - 1) + + heap_sizes.append(right_order) + _trinkle(seq, right_pos, heap_sizes, len(heap_sizes) - 1) + return seq + + +if __name__ == "__main__": + import doctest + import random + + results = doctest.testmod(verbose=False) + assert results.failed == 0, f"{results.failed} doctest(s) failed" + + for trial in range(5000): + sample = random.choices(range(-50, 50), k=random.randint(0, 30)) + got = smoothsort(sample[:]) + assert got == sorted(sample), ( + f"Trial {trial}: smoothsort({sample!r}) -> {got!r}, " + f"expected {sorted(sample)!r}" + ) + + print("All doctests and 5 000 random trials passed.") From de6762004418e11b05f6c87796664a0b1665c78a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 10:11:10 +0000 Subject: [PATCH 5/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- sorts/smoothsort.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py index c6783bdfcb76..ade0423ceaa4 100644 --- a/sorts/smoothsort.py +++ b/sorts/smoothsort.py @@ -10,7 +10,6 @@ https://www.cs.utexas.edu/~EWD/ewd07xx/EWD796a.PDF """ - # Precomputed Leonardo numbers: L(0)=1, L(1)=1, L(k)=L(k-1)+L(k-2)+1. # 46 values comfortably cover all practical list sizes. _LEONARDO: list[int] = [1, 1] @@ -57,8 +56,8 @@ def _sift(seq: list[int], root: int, order: int) -> None: [3, 1, 9, 5, 8] """ while order > 1: - right = root - 1 # right child root - left = root - 1 - _LEONARDO[order - 2] # left child root + right = root - 1 # right child root + left = root - 1 - _LEONARDO[order - 2] # left child root if seq[left] >= seq[right] and seq[left] > seq[root]: seq[root], seq[left] = seq[left], seq[root] @@ -165,10 +164,7 @@ def smoothsort(seq: list[int]) -> list[int]: # ------------------------------------------------------------------ for i in range(n): # If the two rightmost trees have consecutive orders, merge them. - if ( - len(heap_sizes) >= 2 - and heap_sizes[-2] == heap_sizes[-1] + 1 - ): + if len(heap_sizes) >= 2 and heap_sizes[-2] == heap_sizes[-1] + 1: heap_sizes.pop() heap_sizes[-1] += 1 elif heap_sizes and heap_sizes[-1] == 1: From 679422bb1b691bdaf0a87b6f77a7dd071f3db7af Mon Sep 17 00:00:00 2001 From: Pranav M S Krishnan Date: Tue, 26 May 2026 15:48:36 +0530 Subject: [PATCH 6/6] Update comments to use colons instead of dashes --- sorts/smoothsort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/smoothsort.py b/sorts/smoothsort.py index ade0423ceaa4..6f566731fba8 100644 --- a/sorts/smoothsort.py +++ b/sorts/smoothsort.py @@ -160,7 +160,7 @@ def smoothsort(seq: list[int]) -> list[int]: heap_sizes: list[int] = [] # ------------------------------------------------------------------ - # Phase 1 – Build the Leonardo heap forest over seq[0..n-1]. + # Phase 1 : Build the Leonardo heap forest over seq[0..n-1]. # ------------------------------------------------------------------ for i in range(n): # If the two rightmost trees have consecutive orders, merge them. @@ -175,7 +175,7 @@ def smoothsort(seq: list[int]) -> list[int]: _trinkle(seq, i, heap_sizes, len(heap_sizes) - 1) # ------------------------------------------------------------------ - # Phase 2 – Extract maximum elements right-to-left. + # Phase 2 : Extract maximum elements right-to-left. # ------------------------------------------------------------------ for i in range(n - 1, -1, -1): order = heap_sizes.pop()