Skip to content

Commit 3df575e

Browse files
committed
migrated Distances and Grid classes
1 parent e144aeb commit 3df575e

File tree

6 files changed

+163
-69
lines changed

6 files changed

+163
-69
lines changed

algorithms/sidewinder.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from random import randint, choice
2+
from typing import cast
23

4+
from base.cell import Cell
35
from base.grid import Grid
46

57
"""
@@ -26,5 +28,5 @@ def on(grid: Grid) -> Grid:
2628
member.link(member.north)
2729
run.clear()
2830
else:
29-
cell.link(cell.east)
31+
cell.link(cast(Cell, cell.east))
3032
return grid

base/cell.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def link(self, cell: "Cell", bidirectional: bool = True) -> "Cell":
7474
"""
7575
Links current cell to specified one
7676
"""
77-
if not self._is_cell(cell):
77+
if not is_cell(cell):
7878
raise ValueError("Link can only be made between two cells")
7979

8080
self._links[cell] = True
@@ -88,7 +88,7 @@ def unlink(self, cell: "Cell", bidirectional: bool = True) -> "Cell":
8888
"""
8989
if cell is None:
9090
warnings.warn("Attempted to remove non-existant link", UserWarning)
91-
elif not self._is_cell(cell):
91+
elif not is_cell(cell):
9292
raise ValueError("Link can only be removed between two cells")
9393

9494
if self.linked_to(cell):
@@ -99,7 +99,7 @@ def unlink(self, cell: "Cell", bidirectional: bool = True) -> "Cell":
9999

100100
# TODO: this was previously not doing integrity checks. see if worth to make it again restrictive
101101
def linked_to(self, cell: Optional["Cell"]) -> bool:
102-
if self._is_cell(cell):
102+
if is_cell(cell):
103103
return cell in self._links
104104
elif cell is None:
105105
return False
@@ -115,10 +115,6 @@ def random_neighbour(self) -> Optional["Cell"]:
115115
def has_data(self, key: Hashable) -> bool:
116116
return key in self.data.keys()
117117

118-
@staticmethod
119-
def _is_cell(cell: Any) -> bool:
120-
return isinstance(cell, Cell)
121-
122118
def __iadd__(self, cell: "Cell") -> "Cell":
123119
"""
124120
Overload for the += operator with the link method
@@ -147,8 +143,14 @@ def __repr__(self) -> str:
147143

148144
# Note: The following methods actually don't take into account neighbors/linked-cells, but for now is enough
149145

150-
def __eq__(self, other_cell: "Cell") -> bool: # type: ignore
146+
def __eq__(self, other_cell: Optional["Cell"]) -> bool: # type: ignore
147+
if not is_cell(other_cell) or other_cell is None:
148+
return False
151149
return self.row == other_cell.row and self.column == other_cell.column
152150

153151
def __ne__(self, other_cell: "Cell") -> bool: # type: ignore
154152
return not self == other_cell
153+
154+
155+
def is_cell(cell: Any) -> bool:
156+
return isinstance(cell, Cell)

base/distances.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
1-
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING # noqa: F401
1+
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
22

33
# Avoid cyclic import, as Cell uses Distances
44
if TYPE_CHECKING:
55
from base.cell import Cell # noqa: F401
6+
else:
7+
Cell = "Cell"
8+
9+
10+
Cells = Dict[Cell, int]
611

712

813
class Distances:
914

10-
def __init__(self, root: "Cell") -> None:
11-
self.root = root
12-
self._cells = dict() # type: Dict[Cell, int]
15+
def __init__(self, root: Cell) -> None:
16+
if not is_cell(root):
17+
raise ValueError("Root must be a cell")
18+
19+
self.root: Cell = root
20+
self._cells: Cells = dict()
1321
self._cells[root] = 0
1422

15-
def __getitem__(self, cell: "Cell") -> Optional[int]:
16-
try:
23+
def __getitem__(self, cell: Cell) -> Optional[int]:
24+
if not is_cell(cell):
25+
raise IndexError("Distances must be indexed with a cell")
26+
27+
if cell in self._cells.keys():
1728
return self._cells[cell]
18-
except KeyError:
19-
return None
29+
return None
30+
31+
def __setitem__(self, cell: Cell, distance: int) -> None:
32+
if not is_cell(cell):
33+
raise IndexError("Distances must be indexed with a cell")
2034

21-
def __setitem__(self, cell: "Cell", distance: int) -> None:
2235
self._cells[cell] = distance
2336

2437
@property
25-
def cells(self) -> List["Cell"]:
38+
def cells(self) -> List[Cell]:
2639
return list(self._cells.keys())
2740

2841
@property
29-
def max(self) -> Tuple["Cell", int]:
42+
def max(self) -> Tuple[Cell, int]:
3043
max_distance = 0
3144
max_cell = self.root
3245

@@ -37,10 +50,13 @@ def max(self) -> Tuple["Cell", int]:
3750

3851
return max_cell, max_distance
3952

40-
def path_to(self, destination: "Cell") -> "Distances":
53+
def path_to(self, destination: Cell) -> "Distances":
4154
"""
4255
Traverses backwards, from destination to root/origin
4356
"""
57+
if not is_cell(destination):
58+
raise ValueError("Destination must be a cell")
59+
4460
current_cell = destination
4561

4662
breadcrumbs = Distances(self.root)
@@ -54,3 +70,7 @@ def path_to(self, destination: "Cell") -> "Distances":
5470
break
5571

5672
return breadcrumbs
73+
74+
75+
def is_cell(cell: Cell) -> bool:
76+
return type(cell).__name__ == Cell

base/grid.py

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
from random import randint
2-
from typing import cast, Generator, List, Optional
1+
from random import randrange
2+
from typing import Any, cast, Dict, Generator, List, Optional, Tuple
33

4-
from base.cell import Cell
4+
from base.cell import Cell, is_cell
5+
6+
7+
Key = Tuple[int, int]
8+
CellList = List[Cell]
59

610

711
class Grid:
@@ -18,23 +22,28 @@ def columns(self) -> int:
1822
def size(self) -> int:
1923
return self.rows * self.columns
2024

25+
@property
26+
def dimensions(self) -> Key:
27+
return self.rows, self.columns
28+
29+
@property
30+
def data(self) -> Dict:
31+
return self._data
32+
2133
@property
2234
def deadends(self) -> List[Cell]:
23-
deadends_list = []
24-
for cell in self.each_cell():
25-
if len(cell.links) == 1:
26-
deadends_list.append(cell)
27-
return deadends_list
35+
return [cell for cell in self.each_cell() if len(cell.links) == 1]
2836

2937
def __init__(self, rows: int, columns: int) -> None:
3038
if rows is None or rows < 2:
3139
raise ValueError("Rows must be an integer greater than 1")
3240
if columns is None or columns < 2:
3341
raise ValueError("Columns must an integer greater than 1")
3442

35-
self._rows = rows # type: int
36-
self._columns = columns # type: int
37-
self._grid = self.prepare_grid()
43+
self._rows: int = rows
44+
self._columns: int = columns
45+
self._data: Dict = {}
46+
self._grid: List[List[Cell]] = self.prepare_grid()
3847
self.configure_cells()
3948

4049
def cell_at(self, row: int, column: int) -> Optional[Cell]:
@@ -44,32 +53,79 @@ def cell_at(self, row: int, column: int) -> Optional[Cell]:
4453
return None
4554
return self._grid[row][column]
4655

47-
def set_cell_at(self, row: int, column: int, cell: Cell) -> None:
48-
self._grid[row][column] = cell
56+
def set_cell_at(self, row: int, column: int, value: Cell) -> None:
57+
self._grid[row][column] = value
4958

5059
def prepare_grid(self) -> List[List[Cell]]:
5160
return [[Cell(row, column) for column in range(self.columns)] for row in range(self.rows)]
5261

5362
def configure_cells(self) -> None:
63+
"""
64+
Create all the north/sout/east/west dependencies of the cells
65+
"""
5466
for cell in self.each_cell():
55-
cell.north = self.cell_at(cell.row - 1, cell.column)
56-
cell.south = self.cell_at(cell.row + 1, cell.column)
57-
cell.east = self.cell_at(cell.row, cell.column + 1)
58-
cell.west = self.cell_at(cell.row, cell.column - 1)
67+
row = cell.row
68+
column = cell.column
69+
70+
cell.north = self[row - 1, column]
71+
cell.south = self[row + 1, column]
72+
cell.east = self[row, column + 1]
73+
cell.west = self[row, column - 1]
5974

6075
def random_cell(self) -> Cell:
61-
column = randint(0, self.columns - 1)
62-
row = randint(0, self.rows - 1)
63-
return cast(Cell, self.cell_at(row, column))
76+
row = randrange(0, self.rows)
77+
column = randrange(0, self.columns)
78+
return cast(Cell, self[row, column])
6479

65-
def each_row(self) -> Generator:
80+
def each_row(self) -> Generator[CellList, None, None]:
6681
for row in range(self.rows):
6782
yield self._grid[row]
6883

84+
# TODO: Add tests
85+
def each_column(self) -> Generator[CellList, None, None]:
86+
for column in zip(*self._grid):
87+
yield column
88+
6989
def each_cell(self) -> Generator:
70-
for row in range(self.rows):
71-
for column in range(self.columns):
72-
yield self.cell_at(row, column)
90+
for row in self.each_row():
91+
for cell in row:
92+
yield cell
7393

7494
def contents_of(self, cell: Cell) -> str:
7595
return " "
96+
97+
def __getitem__(self, key: Key) -> Optional[Cell]:
98+
if not is_key(key):
99+
raise IndexError('Only grid[row,col] __getitem__ calls are supported')
100+
return self.cell_at(*key)
101+
102+
if is_key(key):
103+
row, column = key
104+
if row < 0 or row > self.rows - 1:
105+
return None
106+
if column < 0 or column > self.columns - 1:
107+
return None
108+
return self._grid[row][column]
109+
110+
def __setitem__(self, key: Key, value: Cell) -> None:
111+
if not (is_key(key) and is_cell(value)):
112+
raise IndexError('Only grid[row,col] __setitem__ calls are supported')
113+
self.set_cell_at(*key, value)
114+
115+
def __contains__(self, other: Cell) -> bool:
116+
if is_cell(other):
117+
for cell in self.each_cell():
118+
if cell == other:
119+
return True
120+
return False
121+
122+
123+
def is_key(key: Key) -> bool:
124+
"""
125+
Runtime check for key correctness
126+
"""
127+
return type(key) == tuple and len(key) == 2 and not any(type(value) != int for value in key)
128+
129+
130+
def is_grid(grid: Any) -> bool:
131+
return isinstance(grid, Grid)

test/test_cell.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_unlinking() -> None:
6363
assert yet_another_cell.linked_to(a_cell) is True
6464

6565

66-
def test_unlinking_using_operator_overrides() -> None:
66+
def test_unlinking_using_operator_overloads() -> None:
6767
a_cell = Cell(1, 1)
6868
another_cell = Cell(1, 2)
6969
yet_another_cell = Cell(2, 1)

test/test_grid.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,53 @@ def test_constructor() -> None:
1616
def test_cell_access() -> None:
1717
grid = Grid(2, 2)
1818

19-
assert grid.cell_at(0, 0) == Cell(0, 0) # type: ignore
20-
assert grid.cell_at(0, 1) == Cell(0, 1) # type: ignore
21-
assert grid.cell_at(1, 0) == Cell(1, 0) # type: ignore
22-
assert grid.cell_at(1, 1) == Cell(1, 1) # type: ignore
19+
assert grid.cell_at(0, 0) == Cell(0, 0)
20+
assert grid.cell_at(0, 1) == Cell(0, 1)
21+
assert grid.cell_at(1, 0) == Cell(1, 0)
22+
assert grid.cell_at(1, 1) == Cell(1, 1)
2323

2424
assert grid.cell_at(-1, 0) is None
2525
assert grid.cell_at(0, -1) is None
2626
assert grid.cell_at(4, 0) is None
2727
assert grid.cell_at(0, 4) is None
2828

2929

30+
def test_cell_access_using_operator_overloads() -> None:
31+
grid = Grid(2, 2)
32+
33+
assert grid[0, 0] == Cell(0, 0)
34+
assert grid[0, 1] == Cell(0, 1)
35+
assert grid[1, 0] == Cell(1, 0)
36+
assert grid[1, 1] == Cell(1, 1)
37+
38+
assert grid[-1, 0] is None
39+
assert grid[0, -1] is None
40+
assert grid[4, 0] is None
41+
assert grid[0, 4] is None
42+
43+
3044
def test_neighbors_setup_when_grid_is_created() -> None:
3145
grid = Grid(2, 2)
3246

33-
assert grid.cell_at(0, 0).north is None # type: ignore
34-
assert grid.cell_at(0, 0).south == Cell(1, 0) # type: ignore
35-
assert grid.cell_at(0, 0).east == Cell(0, 1) # type: ignore
36-
assert grid.cell_at(0, 0).west is None # type: ignore
37-
38-
assert grid.cell_at(0, 1).north is None # type: ignore
39-
assert grid.cell_at(0, 1).south == Cell(1, 1) # type: ignore
40-
assert grid.cell_at(0, 1).east is None # type: ignore
41-
assert grid.cell_at(0, 1).west == Cell(0, 0) # type: ignore
42-
43-
assert grid.cell_at(1, 0).north == Cell(0, 0) # type: ignore
44-
assert grid.cell_at(1, 0).south is None # type: ignore
45-
assert grid.cell_at(1, 0).east == Cell(1, 1) # type: ignore
46-
assert grid.cell_at(1, 0).west is None # type: ignore
47-
48-
assert grid.cell_at(1, 1).north == Cell(0, 1) # type: ignore
49-
assert grid.cell_at(1, 1).south is None # type: ignore
50-
assert grid.cell_at(1, 1).east is None # type: ignore
51-
assert grid.cell_at(1, 1).west == Cell(1, 0) # type: ignore
47+
assert grid[0, 0].north is None # type: ignore
48+
assert grid[0, 0].south == Cell(1, 0) # type: ignore
49+
assert grid[0, 0].east == Cell(0, 1) # type: ignore
50+
assert grid[0, 0].west is None # type: ignore
51+
52+
assert grid[0, 1].north is None # type: ignore
53+
assert grid[0, 1].south == Cell(1, 1) # type: ignore
54+
assert grid[0, 1].east is None # type: ignore
55+
assert grid[0, 1].west == Cell(0, 0) # type: ignore
56+
57+
assert grid[1, 0].north == Cell(0, 0) # type: ignore
58+
assert grid[1, 0].south is None # type: ignore
59+
assert grid[1, 0].east == Cell(1, 1) # type: ignore
60+
assert grid[1, 0].west is None # type: ignore
61+
62+
assert grid[1, 1].north == Cell(0, 1) # type: ignore
63+
assert grid[1, 1].south is None # type: ignore
64+
assert grid[1, 1].east is None # type: ignore
65+
assert grid[1, 1].west == Cell(1, 0) # type: ignore
5266

5367

5468
def test_random_cell() -> None:

0 commit comments

Comments
 (0)