Algorithm Search⌘K

Array in Python

Contiguous block of cells indexed by integer position, with O(1) access by index.

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

from typing import Generic, TypeVar

T = TypeVar("T")


class Array(Generic[T]):
    """A partially filled array of fixed capacity.

    The list held is cells[0:n]. get and set cost O(1). insert at index i
    makes n - i moves and delete at index i makes n - 1 - i moves. search
    makes at most n comparisons. Space: capacity cells.
    """

    def __init__(self, capacity: int = 0) -> None:
        if capacity < 0:
            raise ValueError("capacity must be >= 0")
        self.cells: list[T | None] = [None] * capacity
        self.n = 0
        self.moves = 0  # the number of executions of line L4 or L9, for tests

    @property
    def capacity(self) -> int:
        return len(self.cells)

    def __len__(self) -> int:
        return self.n

    def _check(self, i: int, hi: int) -> None:
        if not 0 <= i < hi:
            raise IndexError("index out of range")

    def get(self, i: int) -> T:
        """Return the item at index i. Precondition: 0 <= i < n. Cost O(1)."""
        self._check(i, self.n)
        return self.cells[i]  # type: ignore[return-value]  # @line L1

    def set(self, i: int, x: T) -> None:
        """Replace the item at index i by x. Precondition: 0 <= i < n. Cost O(1)."""
        self._check(i, self.n)
        self.cells[i] = x  # @line L2

    def insert(self, i: int, x: T) -> None:
        """Insert x at index i. Precondition: 0 <= i <= n < capacity. Cost O(n - i + 1)."""
        self._check(i, self.n + 1)
        if self.n >= self.capacity:
            raise OverflowError("array is full")
        for j in range(self.n, i, -1):  # @line L3
            self.cells[j] = self.cells[j - 1]  # @line L4
            self.moves += 1
        self.cells[i] = x  # @line L5
        self.n += 1  # @line L6

    def delete(self, i: int) -> T:
        """Remove and return the item at index i. Precondition: 0 <= i < n. Cost O(n - i)."""
        self._check(i, self.n)
        x = self.cells[i]  # @line L7
        for j in range(i, self.n - 1):  # @line L8
            self.cells[j] = self.cells[j + 1]  # @line L9
            self.moves += 1
        self.n -= 1  # @line L10
        return x  # type: ignore[return-value]  # @line L11

    def search(self, x: T) -> int | None:
        """Return the first index of x, or None. Cost O(n) comparisons."""
        for i in range(self.n):  # @line S1
            if self.cells[i] == x:  # @line S2
                return i
        return None  # @line S3

    def to_list(self) -> list[T]:
        return list(self.cells[: self.n])  # type: ignore[arg-type]

    def check_invariant(self) -> None:
        assert 0 <= self.n <= self.capacity
test_array.py 92 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/array/. The authors are listed in prov/43DJ9Z.jsonl.

import random

import pytest

from array import Array


def test_page_example() -> None:
    a: Array[int] = Array(8)
    steps = []
    for i, x in [(0, 5), (1, 8), (1, 3), (0, 9)]:
        before = a.moves
        a.insert(i, x)
        steps.append(a.moves - before)
    assert a.to_list() == [9, 5, 3, 8]
    assert steps == [0, 0, 1, 3]
    assert a.get(2) == 3
    a.set(3, 7)
    assert a.to_list() == [9, 5, 3, 7]
    before = a.moves
    assert a.delete(1) == 5
    assert a.moves - before == 2
    assert a.to_list() == [9, 3, 7]
    assert a.cells[3] == 7  # the stale copy stays beyond n
    assert a.search(7) == 2
    assert a.search(4) is None


def test_edges() -> None:
    a: Array[int] = Array(0)
    with pytest.raises(OverflowError):
        a.insert(0, 1)
    b: Array[int] = Array(2)
    with pytest.raises(IndexError):
        b.get(0)
    with pytest.raises(IndexError):
        b.insert(1, 1)  # index n + 1 is not allowed
    b.insert(0, 1)
    b.insert(1, 1)
    with pytest.raises(OverflowError):
        b.insert(0, 2)
    assert b.search(1) == 0  # the first index of an item that occurs twice
    assert b.delete(1) == 1
    assert b.delete(0) == 1
    assert len(b) == 0
    with pytest.raises(IndexError):
        b.delete(0)


def test_moves_count() -> None:
    a: Array[int] = Array(20)
    for k in range(10):
        a.insert(k, k)
    before = a.moves
    a.insert(0, -1)
    assert a.moves - before == 10
    before = a.moves
    a.delete(0)
    assert a.moves - before == 10


def test_random_against_list() -> None:
    rng = random.Random(4301)
    for _ in range(500):
        cap = rng.randint(0, 12)
        a: Array[int] = Array(cap)
        model: list[int] = []
        for _ in range(40):
            op = rng.randrange(4)
            if op == 0 and len(model) < cap:
                i = rng.randint(0, len(model))
                x = rng.randint(0, 5)
                a.insert(i, x)
                model.insert(i, x)
            elif op == 1 and model:
                i = rng.randrange(len(model))
                assert a.delete(i) == model.pop(i)
            elif op == 2 and model:
                i = rng.randrange(len(model))
                x = rng.randint(0, 5)
                a.set(i, x)
                model[i] = x
                assert a.get(i) == x
            else:
                x = rng.randint(0, 5)
                want = model.index(x) if x in model else None
                assert a.search(x) == want
            a.check_invariant()
            assert a.to_list() == model

What the next level needs

  • the tests did not run in this build

Read the full page about Array