Algorithm Search⌘K

Radix select in Python

Find the element of a given rank by examining keys digit by digit and narrowing the candidate buckets.

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

from typing import Optional, Sequence


def radix_select(a: Sequence[int], k: int, r: int = 8, w: Optional[int] = None) -> int:
    """Return the item of rank k (1-based) of a list of nonnegative integers.

    Looks at the keys r bits at a time, from the most significant digit of a
    w-bit key down, and keeps only the bucket that holds rank k. If w is not
    given, it is the bit length of the largest key. Uses no comparisons
    between keys.
    """
    n = len(a)
    if not 1 <= k <= n:
        raise ValueError("k must be between 1 and len(a)")
    if r < 1:
        raise ValueError("r must be at least 1")
    if any(x < 0 for x in a):
        raise ValueError("keys must be nonnegative")
    if w is None:
        w = max(max(a).bit_length(), 1)
    mask = (1 << r) - 1
    s = list(a)  # @line L1
    d = -(-w // r)  # @line L1
    for j in range(d - 1, -1, -1):  # @line L2
        if len(s) == 1:  # @line L3
            return s[0]  # @line L3
        shift = r * j
        count = [0] * (mask + 1)  # @line L4
        for x in s:  # @line L5
            count[(x >> shift) & mask] += 1  # @line L5
        b = 0  # @line L6
        while k > count[b]:  # @line L7
            k -= count[b]  # @line L7
            b += 1  # @line L7
        s = [x for x in s if (x >> shift) & mask == b]  # @line L8
    return s[0]  # @line L9
test_radix_select.py 36 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/radix-select/. The authors are listed in prov/ZPSQHH.jsonl.

import random

import pytest

from radix_select import radix_select


def test_example() -> None:
    a = [31, 41, 59, 26, 53, 58, 97, 93, 23, 84]
    assert radix_select(a, 5, r=4, w=7) == 53
    assert [radix_select(a, k) for k in range(1, 11)] == sorted(a)


def test_edges() -> None:
    assert radix_select([0], 1) == 0
    assert radix_select([0, 0, 0], 2) == 0
    assert radix_select([7, 7, 3, 7], 1, r=1) == 3
    assert radix_select([2**64 - 1, 0, 2**63], 2, r=16) == 2**63
    with pytest.raises(ValueError):
        radix_select([1, 2], 3)
    with pytest.raises(ValueError):
        radix_select([1, -2], 1)


def test_random_against_sorted() -> None:
    rng = random.Random(4096)
    for _ in range(1500):
        n = rng.randint(1, 80)
        bits = rng.choice([1, 3, 8, 20, 40])
        a = [rng.randrange(1 << bits) for _ in range(n)]
        k = rng.randint(1, n)
        r = rng.choice([1, 2, 3, 4, 8])
        assert radix_select(a, k, r=r) == sorted(a)[k - 1]

What the next level needs

  • the tests did not run in this build

Read the full page about Radix select