Skip to content

Commit

Permalink
graph edge case (#28)
Browse files Browse the repository at this point in the history
* graph edge case

* try CI fix
  • Loading branch information
pmaher86 authored Sep 22, 2024
1 parent 42e1673 commit cdadb42
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 16 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ jobs:
pip install -r requirements.txt
- name: Install wkhtmltopdf
run: sudo apt-get -y install wkhtmltopdf
run: |
sudo apt-get update
sudo apt-get -y install wkhtmltopdf
- name: Install package
run: pip install .[dev]
Expand Down
45 changes: 30 additions & 15 deletions src/blacksquare/crossword.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ def __init__(
cells = [Cell(self, (i, j), grid[i][j]) for i, j in np.ndindex(*shape)]
self._grid = np.array(cells, dtype=object).reshape(shape)

if symmetry is not None and symmetry.requires_square and self._num_rows != self._num_cols:
if (
symmetry is not None
and symmetry.requires_square
and self._num_rows != self._num_cols
):
raise ValueError(f"{symmetry.value} symmetry requires a square grid.")

self._numbers = np.zeros_like(self._grid, dtype=int)
Expand Down Expand Up @@ -330,16 +334,20 @@ def _parse_grid(self) -> None:
np.equal(x, BLACK) for x in (shifted_down, shifted_right)
)
too_short_down = np.equal(shifted_up, BLACK) & np.equal(shifted_down, BLACK)
too_short_across = np.equal(shifted_left, BLACK) & np.equal(shifted_right, BLACK)
too_short_across = np.equal(shifted_left, BLACK) & np.equal(
shifted_right, BLACK
)

starts_down = starts_down & ~too_short_down
starts_across = starts_across & ~too_short_across
needs_num = is_open & (starts_down | starts_across)
self._numbers = np.reshape(np.cumsum(needs_num), self._grid.shape) * needs_num
self._across = (
np.maximum.accumulate(starts_across * self._numbers, axis=1) * (is_open & ~too_short_across)
self._across = np.maximum.accumulate(starts_across * self._numbers, axis=1) * (
is_open & ~too_short_across
)
self._down = np.maximum.accumulate(starts_down * self._numbers) * (
is_open & ~too_short_down
)
self._down = np.maximum.accumulate(starts_down * self._numbers) * (is_open & ~too_short_down)

def get_cells_to_nums(ordered_nums: np.ndarray) -> Dict[Tuple[int, ...], int]:
flattened = ordered_nums.ravel()
Expand Down Expand Up @@ -486,7 +494,7 @@ def get_word_at_index(
number = self._get_direction_numbers(direction)[index]
try:
return self[direction, number]
except:
except IndexError:
return None

def set_word(self, word_index: WordIndex, value: str) -> None:
Expand Down Expand Up @@ -526,7 +534,7 @@ def set_cell(self, index: CellIndex, value: CellValue) -> None:
index (CellIndex): The index of the cell.
value (str): The new value of the cell.
"""
cell = self._grid[index]
cell: Cell = self._grid[index]
if value == BLACK:
cell.value = BLACK
images = self.get_symmetric_cell_index(index, force_list=True)
Expand All @@ -542,14 +550,20 @@ def set_cell(self, index: CellIndex, value: CellValue) -> None:
self._parse_grid()
else:
cell.value = value
edge = ((ACROSS, self._across[index]), (DOWN, self._down[index]))
if cell.value == EMPTY:
self._dependency_graph.add_edge(*edge)
elif edge in self._dependency_graph.edges:
self._dependency_graph.remove_edge(*edge)
for wi in edge:
if not self[wi].is_open() and wi in self._dependency_graph.nodes:
self._dependency_graph.remove_node(wi)
words = (cell.get_parent_word(ACROSS), cell.get_parent_word(DOWN))
if all([w is not None for w in words]):
edge = tuple(w.index for w in words)
if cell.value == EMPTY:
self._dependency_graph.add_edge(*edge)
elif edge in self._dependency_graph.edges:
self._dependency_graph.remove_edge(*edge)
for word in words:
if (
word is not None
and not word.is_open()
and word.index in self._dependency_graph.nodes
):
self._dependency_graph.remove_node(word.index)

def copy(self) -> Crossword:
"""Returns a copy of the current crossword, with all linked objects (Words and
Expand All @@ -571,6 +585,7 @@ def get_disconnected_open_subgrids(self) -> List[List[Word]]:
Returns:
List[List[Word]]: A list of open subgrids.
"""

return [
sorted(list(cc)) for cc in nx.connected_components(self._dependency_graph)
]
Expand Down
9 changes: 9 additions & 0 deletions tests/test_crossword.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ def test_unsolvable(self, xw):
solution = xw.fill()
assert solution is None

def test_filled_by_setting_letter(self):
xw = Crossword(5, symmetry=None)
for i in range(5):
for j in range(4):
xw[i, j] = BLACK
xw[i, 4] = "A"
result = xw.fill()
assert result is not None


def test_symmetry_requirements():
with pytest.raises(ValueError):
Expand Down

0 comments on commit cdadb42

Please sign in to comment.