How-to: Visiting all neighbors in a grid
This post presents a simpole Python3 code snippet that visits all neighbors of a cell in a MxN grid.
What is it good for?
Graph problems are sometimes formulated as matrix / 2d array problems that require you to do some kind of search in the matrix.
That is, given a 2d array grid
of size MxN
and starting at some index grid[i][j]
, you need to
make a decision how to move forward based on the adjacent neighbors of grid[i][j]
.
Sometimes you’re only interested in the 4 adjacent neighbors: top, right, bottom, left:
| | top | |
|left| x |right|
| |bottom| |
Other times, you might be asked to check all 8 adjacent neighbours, including the diagonal ones left out above.
Some Leetcode examples would be:
1091. Shortest Path in Binary Matrix is an an example where you need to visit all 8 neighbors.
How to do it: the naive way?
When I started with these kind of problems, I had some naive approach where for each index, I computed its neighbors, e.g. in the 4-neighbor case:
top, right, bottom, left = row - 1, col + 1, row + 1, col - 1
Since you need to make sure you don’t pass the boundaries of the grid, I simply added four if-clauses:
top, right, bottom, left = row - 1, col + 1, row + 1, col - 1
if top >= 0 and grid[top][col] == `some-val`:
# do something
if right < self.n and grid[row][right] == `some-val`:
# do something
if bottom < self.m and grid[bottom][col] == `some-val`:
# do something
if left >= 0 and grid[row][left] == `some-val`:
# do something
Needless to say this is hard to read and increases the likelihood of a bug because it’s easy to make a
mistake when indexing into the grid in each if
-clause.
How to do it: the concise way
There’s a very neat way to do this in an iterative way.
- Define your directions, e.g.:
[(-1, 0), (0, 1), (1, 0), (0, -1)]
- Iterate over each direction and compute the new neighbor via:
neighbor_row, neighbor_col = row + d[0], col + d[1]
- use a single
if
-clause to check your boundaries:if ROWS > neighbor_row >= 0 and COLS > neighbor_col >= 0:
Glueing this together, an example could look like so:
ROWS, COLS = len(grid), len(grid[0])
# some more code ...
# top, right, bottom, left
directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
while queue:
row, col = queue.popleft()
for d in directions:
neighbor_row, neighbor_col = row + d[0], col + d[1]
if ROWS > neighbor_row >= 0 and COLS > neighbor_col >= 0:
# do something, e.g. BFS ...
The improvement in readability is huge, isn’t it? And the if
-clause looks also trivial compared to the
ones above.
Bonus: what about all 8 directions?
You have to change only a single line:
# order:
# top, top right, right, bottom right, bottom, bottom left, left, top left
directions = [(-1, 0), (-1, 1), (0, 1), (1,1), (1, 0), (1,-1), (0, -1), (-1,-1)]
That’s it.