Han's integer sorting in Python
Deterministic word-RAM algorithm that sorts n integers of w bits in O(n log log n) time.
- File
han_integer_sort.py, 56 lines- Level
- I0 not checked yet
- Toolchain
- Python 3.14, pytest 9
- Test framework
- pytest
- Run the tests
python3 -m pytest
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/han-integer-sort/. The authors are listed in prov/0V80XQ.jsonl.
from bisect import bisect_left
from math import isqrt
def _insertion_sort(a: list[int]) -> list[int]:
for i in range(1, len(a)):
x = a[i]
j = i - 1
while j >= 0 and a[j] > x:
a[j + 1] = a[j]
j -= 1
a[j + 1] = x
return a
def han_integer_sort(a: list[int], base: int = 8) -> list[int]:
"""Return the integers of a in non-decreasing order.
Precondition: a holds integers (any sign) and base >= 2. This is a model of
the structure of Han's algorithm: a recursion on about sqrt(n) splitters,
as in an exponential search tree, with a deterministic sample. Han passes
the keys down each level in O(1) amortized time per key with packed words,
which gives O(n log log n) time in total. Here each key is placed by binary
search, so one level costs O(n log n), and the sample is not balanced on
every input. The extra space is O(n).
"""
if base < 2:
raise ValueError("base must be at least 2")
return _sort(list(a), base)
def _sort(a: list[int], base: int) -> list[int]:
n = len(a)
if n <= base: # @line L1
return _insertion_sort(a)
k = isqrt(n - 1) + 1 # @line L2
sample = a[::k] # @line L3
p = _sort(sample, base) # @line L4
splitters = [x for i, x in enumerate(p) if i == 0 or x != p[i - 1]]
s = len(splitters)
parts: list[list[int]] = [[] for _ in range(s + 1)]
equal = [0] * s
for x in a: # @line L5
j = bisect_left(splitters, x)
if j < s and splitters[j] == x: # @line L6
equal[j] += 1
else:
parts[j].append(x)
out: list[int] = []
for j in range(s + 1): # @line L7
out.extend(_sort(parts[j], base))
if j < s:
out.extend([splitters[j]] * equal[j])
return out # @line L8
test_han_integer_sort.py 38 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/han-integer-sort/. The authors are listed in prov/0V80XQ.jsonl.
import random
import unittest
from han_integer_sort import han_integer_sort
class TestHanIntegerSort(unittest.TestCase):
def test_page_example(self):
a = [41, 7, 93, 18, 65, 30, 7, 88, 51, 24, 76, 3, 59, 12, 97, 35]
self.assertEqual(a[::4], [41, 65, 51, 59])
self.assertEqual(han_integer_sort(a, base=4), sorted(a))
self.assertEqual(a[0], 41)
def test_edges(self):
for a in ([], [1], [2, 1], [5] * 50, [0, -5, 3, -1, -5, 2], [2**63 - 1, -(2**63), 0, -1, 1] * 4):
self.assertEqual(han_integer_sort(a, base=2), sorted(a))
def test_patterns(self):
for a in (list(range(3000)), list(range(3000, 0, -1)), [i % 17 for i in range(3000)], [2**k for k in range(63)] * 5):
self.assertEqual(han_integer_sort(a), sorted(a))
def test_bad_base(self):
with self.assertRaises(ValueError):
han_integer_sort([2, 1], base=1)
def test_random(self):
rng = random.Random(2002)
for _ in range(600):
n = rng.randint(0, 150)
hi = rng.choice([3, 1000, 2**64])
a = [rng.randint(-hi, hi) for _ in range(n)]
self.assertEqual(han_integer_sort(a, base=rng.choice([2, 4, 8])), sorted(a))
if __name__ == "__main__":
unittest.main()
What the next level needs
- the tests did not run in this build