Coverage for src / hodoku / core / cell_set.py: 100%
65 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-21 08:35 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-21 08:35 +0000
1"""81-cell bitset backed by a Python int."""
3from __future__ import annotations
5from typing import Iterator
7_ALL_BITS = (1 << 81) - 1
10class CellSet:
11 """Bitset over the 81 cells of a sudoku grid (indices 0-80).
13 Backed by a single Python int. Bit i corresponds to cell i.
14 """
16 __slots__ = ("_bits",)
18 def __init__(self, bits: int = 0) -> None:
19 self._bits = bits
21 # --- mutation ---
23 def add(self, index: int) -> None:
24 self._bits |= 1 << index
26 def remove(self, index: int) -> None:
27 self._bits &= ~(1 << index)
29 def clear(self) -> None:
30 self._bits = 0
32 def set_all(self) -> None:
33 self._bits = _ALL_BITS
35 def set(self, other: CellSet) -> None:
36 self._bits = other._bits
38 # --- set operations (in-place) ---
40 def and_(self, other: CellSet) -> None:
41 self._bits &= other._bits
43 def or_(self, other: CellSet) -> None:
44 self._bits |= other._bits
46 def and_not(self, other: CellSet) -> None:
47 self._bits &= ~other._bits
49 # --- set operations (returning new CellSet) ---
51 def intersection(self, other: CellSet) -> CellSet:
52 return CellSet(self._bits & other._bits)
54 def union(self, other: CellSet) -> CellSet:
55 return CellSet(self._bits | other._bits)
57 def difference(self, other: CellSet) -> CellSet:
58 return CellSet(self._bits & ~other._bits)
60 # --- query ---
62 def contains(self, index: int) -> bool:
63 return bool(self._bits >> index & 1)
65 def is_empty(self) -> bool:
66 return self._bits == 0
68 def size(self) -> int:
69 return self._bits.bit_count()
71 def get(self, n: int) -> int:
72 """Return the index of the nth set bit (0-based)."""
73 bits = self._bits
74 for _ in range(n):
75 bits &= bits - 1 # clear lowest set bit
76 return (bits & -bits).bit_length() - 1
78 def first(self) -> int:
79 """Return the lowest set index, or -1 if empty."""
80 if self._bits == 0:
81 return -1
82 return (self._bits & -self._bits).bit_length() - 1
84 def equals(self, other: CellSet) -> bool:
85 return self._bits == other._bits
87 # --- iteration ---
89 def __iter__(self) -> Iterator[int]:
90 bits = self._bits
91 while bits:
92 lsb = bits & -bits
93 yield lsb.bit_length() - 1
94 bits ^= lsb
96 def __len__(self) -> int:
97 return self._bits.bit_count()
99 def __eq__(self, other: object) -> bool:
100 if isinstance(other, CellSet):
101 return self._bits == other._bits
102 return NotImplemented
104 def __hash__(self) -> int:
105 return hash(self._bits)
107 def __repr__(self) -> str:
108 indices = list(self)
109 return f"CellSet({indices})"
111 def clone(self) -> CellSet:
112 return CellSet(self._bits)