RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Count Number of One Bits

R
from timeit import timeit


def get_set_bits_count_using_brian_kernighans_algorithm(number: int) -> int:
    """
    Count the number of set bits in a 32 bit integer
    >>> get_set_bits_count_using_brian_kernighans_algorithm(25)
    3
    >>> get_set_bits_count_using_brian_kernighans_algorithm(37)
    3
    >>> get_set_bits_count_using_brian_kernighans_algorithm(21)
    3
    >>> get_set_bits_count_using_brian_kernighans_algorithm(58)
    4
    >>> get_set_bits_count_using_brian_kernighans_algorithm(0)
    0
    >>> get_set_bits_count_using_brian_kernighans_algorithm(256)
    1
    >>> get_set_bits_count_using_brian_kernighans_algorithm(-1)
    Traceback (most recent call last):
        ...
    ValueError: the value of input must not be negative
    >>> get_set_bits_count_using_brian_kernighans_algorithm(1023)
    10
    """
    if number < 0:
        raise ValueError("the value of input must not be negative")
    result = 0
    while number:
        number &= number - 1
        result += 1
    return result


def get_set_bits_count_using_modulo_operator(number: int) -> int:
    """
    Count the number of set bits in a 32 bit integer
    >>> get_set_bits_count_using_modulo_operator(25)
    3
    >>> get_set_bits_count_using_modulo_operator(37)
    3
    >>> get_set_bits_count_using_modulo_operator(21)
    3
    >>> get_set_bits_count_using_modulo_operator(58)
    4
    >>> get_set_bits_count_using_modulo_operator(0)
    0
    >>> get_set_bits_count_using_modulo_operator(256)
    1
    >>> get_set_bits_count_using_modulo_operator(-1)
    Traceback (most recent call last):
        ...
    ValueError: the value of input must not be negative
    >>> get_set_bits_count_using_modulo_operator(1024)
    1
    """
    if number < 0:
        raise ValueError("the value of input must not be negative")
    result = 0
    while number:
        if number % 2 == 1:
            result += 1
        number >>= 1
    return result


def get_set_bits_count_using_lookup_table(number: int) -> int:
    """
    Count the number of set bits in a 32-bit integer using a precomputed lookup table.

    I see similar approach in GeeksforGeeks, but the implementation is different.
    Link to Code:
    https://www.geeksforgeeks.org/dsa/count-set-bits-integer-using-lookup-table/

    >>> get_set_bits_count_using_lookup_table(25)
    3
    >>> get_set_bits_count_using_lookup_table(37)
    3
    >>> get_set_bits_count_using_lookup_table(21)
    3
    >>> get_set_bits_count_using_lookup_table(58)
    4
    >>> get_set_bits_count_using_lookup_table(0)
    0
    >>> get_set_bits_count_using_lookup_table(256)
    1
    >>> get_set_bits_count_using_lookup_table(-1)
    Traceback (most recent call last):
        ...
    ValueError: the value of input must not be negative
    """
    _lookup_table = [bin(i).count("1") for i in range(256)]

    if number < 0:
        raise ValueError("the value of input must not be negative")

    # Split 32-bit number into four 8-bit chunks and use lookup table
    return (
        _lookup_table[number & 0xFF]
        + _lookup_table[(number >> 8) & 0xFF]
        + _lookup_table[(number >> 16) & 0xFF]
        + _lookup_table[(number >> 24) & 0xFF]
    )


def benchmark() -> None:
    """
    Benchmark code for comparing 3 functions, with different length int values.
    Brian Kernighan's algorithm is consistently faster than using modulo_operator,
    and the lookup table method is often the fastest for repeated calls.
    """

    def do_benchmark(number: int) -> None:
        setup = "import __main__ as z"
        print(f"Benchmark when {number = }:")

        print(f"{get_set_bits_count_using_modulo_operator(number) = }")
        timing = timeit(
            f"z.get_set_bits_count_using_modulo_operator({number})", setup=setup
        )
        print(f"timeit() runs in {timing} seconds")

        print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }")
        timing = timeit(
            f"z.get_set_bits_count_using_brian_kernighans_algorithm({number})",
            setup=setup,
        )
        print(f"timeit() runs in {timing} seconds")

        print(f"{get_set_bits_count_using_lookup_table(number) = }")
        timing = timeit(
            f"z.get_set_bits_count_using_lookup_table({number})",
            setup=setup,
        )
        print(f"timeit() runs in {timing} seconds")

    for number in (25, 37, 58, 0):
        do_benchmark(number)
        print()


if __name__ == "__main__":
    import doctest

    doctest.testmod()
    benchmark()