Han-Thorup randomized integer sorting in Python
Randomized word-RAM integer sort with expected time O(n sqrt(log log n)), faster than deterministic bounds.
- File
han_thorup_integer_sort.py, 65 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-thorup-integer-sort/. The authors are listed in prov/B8DZ6B.jsonl.
import random
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_thorup_integer_sort(a: list[int], base: int = 8, seed: int | None = None) -> 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 the Han and Thorup algorithm: split the keys into ordered
parts by a random sample of about sqrt(n) splitters, check that no part is
too large, and recurse. The paper splits with signature sort and packed
words in O(n sqrt(log log n)) expected time in total. Here each key is
placed by binary search, so the expected time is O(n log n), and the extra
space is O(n). The output does not depend on the seed.
"""
if base < 2:
raise ValueError("base must be at least 2")
return _sort(list(a), base, random.Random(seed))
def _split(a: list[int], splitters: list[int]) -> tuple[list[list[int]], list[int]]:
s = len(splitters)
parts: list[list[int]] = [[] for _ in range(s + 1)]
equal = [0] * s
for x in a:
j = bisect_left(splitters, x)
if j < s and splitters[j] == x:
equal[j] += 1
else:
parts[j].append(x)
return parts, equal
def _sort(a: list[int], base: int, rng: random.Random) -> list[int]:
n = len(a)
if n <= base: # @line L1
return _insertion_sort(a)
k = isqrt(n - 1) + 1 # @line L2
for attempt in range(4):
sample = [a[rng.randrange(n)] for _ in range(k)] # @line L3
p = _sort(sample, base, rng) # @line L4
splitters = [x for i, x in enumerate(p) if i == 0 or x != p[i - 1]]
parts, equal = _split(a, splitters) # @line L5
if max(len(q) for q in parts) <= n // 2 or attempt == 3: # @line L6
break
out: list[int] = []
for j in range(len(parts)): # @line L7
out.extend(_sort(parts[j], base, rng))
if j < len(equal):
out.extend([splitters[j]] * equal[j])
return out # @line L8
test_han_thorup_integer_sort.py 41 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/han-thorup-integer-sort/. The authors are listed in prov/B8DZ6B.jsonl.
import random
import unittest
from han_thorup_integer_sort import _split, han_thorup_integer_sort
class TestHanThorupIntegerSort(unittest.TestCase):
def test_page_example(self):
a = [41, 7, 93, 18, 65, 30, 7, 88, 51, 24, 76, 3, 59, 12, 97, 35]
parts, equal = _split(a, [18, 51, 88])
self.assertEqual(parts, [[7, 7, 3, 12], [41, 30, 24, 35], [65, 76, 59], [93, 97]])
self.assertEqual(equal, [1, 1, 1])
for seed in range(20):
self.assertEqual(han_thorup_integer_sort(a, base=4, seed=seed), sorted(a))
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_thorup_integer_sort(a, base=2, seed=1), 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_thorup_integer_sort(a, seed=7), sorted(a))
def test_bad_base(self):
with self.assertRaises(ValueError):
han_thorup_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)]
got = han_thorup_integer_sort(a, base=rng.choice([2, 4, 8]), seed=rng.randrange(10**6))
self.assertEqual(got, sorted(a))
if __name__ == "__main__":
unittest.main()
What the next level needs
- the tests did not run in this build
Read the full page about Han-Thorup randomized integer sorting