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
# 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