More spatial Logic
We want to write a function that returns true if there are three consecutive puppies any direction (left, right, up, down, diagonal).
Let’s imagine we are checking a grid, moving from left to right:
We do not need to also check from right to left! Any group of consecutive puppies we find going right would also be found going left — but we don’t need to check both. The same applies to lookup up/down and diagonally.
This limits our searching a bit. We only need to check four directions:
- going right
- going down
- going down and to the left
- going down and to the right
Using guards
Let’s re-use the is_puppy()
function with guards. We wrote this previously.
def is_puppy(grid, row, column):
num_rows = len(grid)
num_columns = len(grid[0])
# check if the row is off the grid
if row < 0 or row >= num_rows:
return False
# check if the column is off the grid
if column < 0 or column >= num_columns:
return False
if grid[row][column] == 'p':
return True
return False
This will ensure we never check out of bounds on the grid.
Checking for consecutive puppies
Now we can write functions checking for consecutive puppies without worrying about the edges of the grid. First, a function to look to the right:
def has_three_right(grid, row, column):
for c in range(column, column + 3):
if not is_puppy(grid, row, c):
return False
return True
We look at three columns in a row, starting with our current column. And if there is not a puppy in any of these cells, we can immediately return False
. If we get through all three columns and find a puppy everywhere, we return True
.
Similarly, we can write a function to check down:
def has_three_down(grid, row, column):
for r in range(row, row + 3):
if not is_puppy(grid, r, column):
return False
return True
a function to check down and to the left:
def has_three_down_left(grid, row, column):
for r, c in zip(range(row, row + 3), range(column, column - 3, -1)):
if not is_puppy(grid, r, c):
return False
return True
and a function to check down and to the right:
def has_three_down_right(grid, row, column):
for r, c in zip(range(row, row + 3), range(column, column + 3)):
if not is_puppy(grid, r, c):
return False
return True
Notice that for these last two we use zip
! This lets us look diagonally. For example, to go up and to the right, we are zipping the lists [row, row + 1, row +2]
and [column, column + 1, column + 2]
to get a list of tuples: [(row, column), (row + 1, column + 1), (row + 2, column + 2)]
.
In addition, in all of these functions we use r
for ranges counting rows and c
for ranges counting columns, so we have to be sure to use r
and/or c
when checking for puppies.
Checking for three in a row
Now we can write the primary function:
def has_three_in_a_row(grid, value='X'):
num_rows = len(grid)
num_columns = len(grid[0])
for row in range(num_rows):
for column in range(num_columns):
if has_three_right(grid, row, column)
or has_three_down(grid, row, column) \
or has_three_down_left(grid, row, column) \
or has_three_down_right(grid, row, column):
return True
return False
We are using
\
to continue theif
statement on multiple lines since it is really long
Running the code
We can run this code with:
park1 = [
[None, None, 'p', 'p'],
[None, 'p', 'p', 'p'],
[None, None, None, None],
[None, 'p', None, None]
]
park2 = [
[None, None, 'p', 'p'],
[None, None, 'p', None],
[None, 'p', None, 'p'],
[None, 'p', None, None]
]
park3 = [
[None, None, None],
[None, None, None],
[None, None, None]
]
print(has_three_in_a_row(park1))
print(has_three_in_a_row(park2))
print(has_three_in_a_row(park3))
We get:
True
True
False