Algorithm Search⌘K

MSD radix sort in Python

Partitions keys by their leading digit, then recursively sorts each bucket on the next digit.

File
msd_radix_sort.py, 43 lines
Level
I0 not checked yet
Toolchain
Python 3.14, pytest 9
Test framework
pytest
Run the tests
python3 -m pytest
msd_radix_sort.pyRawGitHub
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/msd-radix-sort/. The authors are listed in prov/5YMPGZ.jsonl.


def _c(s: str, d: int) -> int:
    """The bucket of s at depth d: 0 when s has ended, else the code of s[d] plus 1."""
    return ord(s[d]) + 1 if d < len(s) else 0


def msd_radix_sort(a: list[str], r: int = 256) -> list[str]:
    """Sort the list a of strings in lexicographic order, in place, and return it.

    Precondition: each character has a code less than r. The sort is stable.
    The cost is O(r (n + D)) time, where D is the total length of the
    distinguishing prefixes, and Theta(n + r) extra space for the buffer.
    An explicit stack replaces the recursion, so long common prefixes are safe.
    """
    for s in a:
        for ch in s:
            if ord(ch) >= r:
                raise ValueError("a character is outside the alphabet")
    stack = [(0, len(a), 0)]  # @line L1
    while stack:  # @line L2
        lo, hi, d = stack.pop()  # @line L3
        if hi - lo <= 1:  # @line L4
            continue
        count = [0] * (r + 1)  # @line L5
        for i in range(lo, hi):
            count[_c(a[i], d)] += 1
        start = [0] * (r + 2)  # @line L6
        for v in range(r + 1):
            start[v + 1] = start[v] + count[v]
        nxt = start[:]
        buf = [""] * (hi - lo)
        for i in range(lo, hi):  # @line L7
            v = _c(a[i], d)
            buf[nxt[v]] = a[i]
            nxt[v] += 1
        a[lo:hi] = buf  # @line L8
        for v in range(r, 0, -1):  # @line L9
            if start[v + 1] - start[v] >= 2:
                stack.append((lo + start[v], lo + start[v + 1], d + 1))  # @line L10
    return a  # @line L11
test_msd_radix_sort.py 32 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/msd-radix-sort/. The authors are listed in prov/5YMPGZ.jsonl.

import random

from msd_radix_sort import msd_radix_sort


def test_example() -> None:
    a = ["she", "sells", "sea", "shells", "by", "the", "sea", "shore"]
    assert msd_radix_sort(a) == ["by", "sea", "sea", "sells", "she", "shells", "shore", "the"]


def test_edges() -> None:
    assert msd_radix_sort([]) == []
    assert msd_radix_sort(["x"]) == ["x"]
    assert msd_radix_sort(["", "a", ""]) == ["", "", "a"]
    assert msd_radix_sort(["ab", "a", "abc", "a"]) == ["a", "a", "ab", "abc"]
    assert msd_radix_sort(["b", "a"], r=128) == ["a", "b"]


def test_long_common_prefix() -> None:
    p = "q" * 5000
    a = [p + "b", p, p + "a", p]
    assert msd_radix_sort(a) == sorted(a)


def test_random_against_sorted() -> None:
    rng = random.Random(3301)
    for _ in range(600):
        a = ["".join(rng.choice("abc") for _ in range(rng.randint(0, 6))) for _ in range(rng.randint(0, 30))]
        assert msd_radix_sort(list(a)) == sorted(a)

What the next level needs

  • the tests did not run in this build

Read the full page about MSD radix sort