Algorithm Search⌘K

Circular Kadane algorithm in Python

Answer is the larger of plain Kadane and the total sum minus the minimum subarray sum.

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

from typing import Union

Number = Union[int, float]


def circular_kadane(a: list[Number]) -> Number:
    """Return the largest sum of a non-empty circular subarray of a.

    A circular subarray may wrap from the end of a to its start; no item is used twice.
    Precondition: a is not empty (raises ValueError otherwise).
    Cost: O(n) time, O(1) extra space.
    """
    if not a:
        raise ValueError("the array must not be empty")
    hi = best = lo = worst = total = a[0]  # @line L1
    for j in range(1, len(a)):  # @line L2
        hi = a[j] + max(hi, 0)  # @line L3
        best = max(best, hi)
        lo = a[j] + min(lo, 0)  # @line L4
        worst = min(worst, lo)
        total += a[j]  # @line L5
    if best < 0:  # @line L6
        return best
    return max(best, total - worst)  # @line L7


def circular_kadane_arc(a: list[Number]) -> tuple[Number, int, int]:
    """Return (best, start, length): a best circular arc a[start], a[start + 1 mod n], ...

    Same precondition and cost as circular_kadane. Ties prefer a plain arc.
    """
    if not a:
        raise ValueError("the array must not be empty")
    n = len(a)
    hi = best = lo = worst = total = a[0]
    hs = ls = 0
    bi, bj = 0, 0
    wi, wj = 0, 0
    for j in range(1, n):
        if hi < 0:
            hi, hs = a[j], j
        else:
            hi += a[j]
        if hi > best:
            best, bi, bj = hi, hs, j
        if lo > 0:
            lo, ls = a[j], j
        else:
            lo += a[j]
        if lo < worst:
            worst, wi, wj = lo, ls, j
        total += a[j]
    if best < 0 or best >= total - worst or (wi, wj) == (0, n - 1):
        return best, bi, bj - bi + 1
    return total - worst, (wj + 1) % n, n - (wj - wi + 1)
test_circular_kadane_algorithm.py 56 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/circular-kadane-algorithm/. The authors are listed in prov/00F99T.jsonl.

import random

import pytest

from circular_kadane_algorithm import circular_kadane, circular_kadane_arc


def brute(a: list[int]) -> int:
    n = len(a)
    return max(
        sum(a[(s + k) % n] for k in range(length))
        for s in range(n)
        for length in range(1, n + 1)
    )


def arc_sum(a: list[int], start: int, length: int) -> int:
    return sum(a[(start + k) % len(a)] for k in range(length))


def test_example() -> None:
    assert circular_kadane([8, -4, 3, -5, 4]) == 12
    assert circular_kadane_arc([8, -4, 3, -5, 4]) == (12, 4, 2)
    assert circular_kadane([5, -3, 5]) == 10
    assert circular_kadane([1, -2, 3, -2]) == 3


def test_edges() -> None:
    assert circular_kadane([-3, -2, -3]) == -2
    assert circular_kadane_arc([-3, -2, -3]) == (-2, 1, 1)
    assert circular_kadane([7]) == 7
    assert circular_kadane([-7]) == -7
    assert circular_kadane([5, -10]) == 5
    assert circular_kadane([-1, 0]) == 0
    assert circular_kadane([1, 2, 3]) == 6
    assert circular_kadane([0.5, -1.0, 0.5]) == 1.0
    with pytest.raises(ValueError):
        circular_kadane([])
    with pytest.raises(ValueError):
        circular_kadane_arc([])


def test_against_brute_force() -> None:
    rng = random.Random(20261105)
    for _ in range(3000):
        n = rng.randrange(1, 15)
        a = [rng.randrange(-10, 11) for _ in range(n)]
        expected = brute(a)
        assert circular_kadane(a) == expected
        value, start, length = circular_kadane_arc(a)
        assert value == expected
        assert 0 <= start < n and 1 <= length <= n
        assert arc_sum(a, start, length) == expected

What the next level needs

  • the tests did not run in this build

Read the full page about Circular Kadane algorithm