To start this guide, download this zip file.
Grids
A grid is a list of lists. You can think of this as a two-dimensional structure. For example, if we have this grid:
numbers = [
[1, 2, 3],
[4, 5, 6]
]
Then we can visualize it like this:
You can also write it like this:
numbers = [[1, 2, 3],[4, 5, 6]]
But it is easier to visualize the first way.
In some programming languages you will see these called an array. In math you might call this a matrix.
Printing a grid
It helps to think of a grid as a list of rows. Look at the code in
print_grid.py
, which will print out a grid:
def print_grid(grid: list[list[int]]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a space after it, not a newline
print(item, end=' ')
print()
if __name__ == '__main__':
numbers = [
[1, 2, 3],
[4, 5, 6]
]
print_grid(numbers)
If we run this code then we get:
1 2 3
4 5 6
There are a couple of new things going on with this code. Let’s explain them:
Nested for loops
First, you will notice that we have nested for loops in this code:
def print_grid(grid: list[list[int]]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a space after it, not a newline
print(item, end=' ')
print()
Since our grid is a set of nested lists, here is how this works:
Loop #1 | Loop #2 | row | item |
---|---|---|---|
1 | 1 | [1, 2, 3] | 1 |
1 | 2 | [1, 2, 3] | 2 |
1 | 3 | [1, 2, 3] | 3 |
2 | 1 | [4, 5, 6] | 4 |
2 | 2 | [4, 5, 6] | 5 |
2 | 3 | [4, 5, 6] | 6 |
Keyword arguments
Second, you will notice some different syntax with print()
:
print(item, end=' ')
Normally, print()
ends a line with a newline. But when we use end=' '
we are
telling print()
to end the line with a space instead.
This will print one of the numbers, followed by a space instead of a newline.
This ensures all of the numbers on the same row appear on the same line:
1 2 3
. After we done with all of the numbers in a row, we call print()
. We
don’t tell it to print anything, but print always ends with a newline, unless we
override this with end
, so print()
by itself prints a newline to end the
row.
So technically, the code above prints:
1<space>2<space>3<space><newline>
4<space>5<space>6<space><newline>
If we leave off end=' '
in the code above, we would get:
1
2
3
4
5
6
We could also have written this code to print a grid:
def print_grid(grid: list[list[int]]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a hyphen after it, not a newline
print(item, end='-')
print()
This will print a hyphen after each item:
1-2-3-
4-5-6-
Creating a grid
Sometimes you want to create a grid and fill it with whatever you want in each row and column. Here is a function that will do that:
def empty_grid(num_rows: int, num_columns: int, value=None) -> list[list]:
"""
Create an empty grid. <num_rows> is the number
of rows in the grid. <num_columns> is the number
of columns in the grid. Fill the grid with None or with
whatever value the caller supplies in <value>.
"""
# create an empty grid
new_grid = []
# keep going until we have created all the rows
while len(new_grid) < num_rows:
new_row = []
# keep going until we have all the columns we need
while len(new_row) < num_columns:
# append an item for this column
new_row.append(value)
# now that we have a full row, append the row
new_grid.append(new_row)
return new_grid
The function takes three parameters:
num_rows
is the number of rowsnum_columns
is the number of columnsvalue
is the value to put in each place in the grid
Keyword argument
When we use value=None
in the empty_grid()
function, this is a keyword
argument. The first time we call this function:
grid1 = empty_grid(3, 2)
print(grid1)
we have left off the value
argument. This tells empty_grid()
that the first
grid should be filled with None
, since that is the default value we have
specified.
The second time we call this function:
grid2 = empty_grid(3, 2, value='*')
print(grid2)
we have specified value='*'
. This tells empty_grid()
that the second grid
should be filled with *
characters.
Thus when we run this code, we get:
None None
None None
None None
* *
* *
* *
The rest of the function
Now let’s go back and look at the rest of the code in that function:
def empty_grid(num_rows: int, num_columns: int, value=None) -> list[list]:
"""
Create an empty grid. <num_rows> is the number
of rows in the grid. <num_columns> is the number
of columns in the grid. Fill the grid with None or with
whatever value the caller supplies in <value>.
"""
# create an empty grid
new_grid = []
# keep going until we have created all the rows
while len(new_grid) < num_rows:
new_row = []
# keep going until we have all the columns we need
while len(new_row) < num_columns:
# append an item for this column
new_row.append(value)
# now that we have a full row, append the row
new_grid.append(new_row)
return new_grid
We have two, nested while loops here. The first while loop creates the rows. The second while loop creates the columns in each row.
Remember, a grid is just a set of nested lists. So we first create new_grid
to
hold an empty list for the entire grid. Then we create a new_row
to hold each
row. We append the columns, then append new_row
to new_grid
.
Loop # 1 | Loop #2 | new_grid | new_row |
---|---|---|---|
1 | 1 | [] | ['*'] |
1 | 2 | [] | ['*','*'] |
1 | 3 | [] | ['*', '*', '*'] |
1 | done | [['*', '*', '*']] | ['*', '*', '*'] |
2 | 1 | [['*', '*', '*']] | ['*'] |
2 | 2 | [['*', '*', '*']] | ['*','*'] |
2 | 3 | [['*', '*', '*']] | ['*', '*', '*'] |
2 | done | [['*', '*', '*'],['*','*','*']] | ['*', '*', '*'] |
A complete program
The file called empty_grid.py
is a complete program showing how this works:
def empty_grid(num_rows: int, num_columns: int, value=None) -> list[list]:
"""
Create an empty grid. <num_rows> is the number
of rows in the grid. <num_columns> is the number
of columns in the grid. Fill the grid with None or with
whatever value the caller supplies in <value>.
"""
# create an empty grid
new_grid = []
# keep going until we have created all the rows
while len(new_grid) < num_rows:
new_row = []
# keep going until we have all the columns we need
while len(new_row) < num_columns:
# append an item for this column
new_row.append(value)
# now that we have a full row, append the row
new_grid.append(new_row)
return new_grid
def print_grid(grid: list[list]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a space after it, not a newline
print(item, end=' ')
print()
if __name__ == '__main__':
grid1 = empty_grid(3, 2)
print_grid(grid1)
print()
grid2 = empty_grid(3, 2, value='*')
print_grid(grid2)
When you run this code, it should print:
None None
None None
None None
* *
* *
* *
Reading a grid from a file
You may need to read a grid from a file. We will give you files where the grid
is stored with spaces between the values. For example, the file
grid_example.txt
contains this:
. * . ! . * .
* . ! . ! . *
. * . ! . * .
We have some code in read_grid.py
that will read this grid and print it back
out:
def readgrid(filename: str) -> list[list[str]]:
'''
Read a grid from the file given by <filename> and return
a grid object. The file should have all of the grid items
separated by a space in each row.
'''
# read all of the lines of the file given
# by <filename>
with open(filename) as file:
lines = file.readlines()
# create an empty grid
grid = []
# for each line
for line in lines:
# split the line into a list, based on spaces
# append that list as the next row in the grid
grid.append(line.split())
# return the grid
return grid
def print_grid(grid: list[list]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a space after it, not a newline
print(item, end=' ')
print()
if __name__ == '__main__':
grid = readgrid('grid_example.txt')
print_grid(grid)
Note that when we first read all of the lines of the file. Then as we look at
each line, we use split()
to split the line into a list of “words”. Each word
is one of the items in the grid. And this list is exactly what we need for a
row! This means we can directly append that list to the grid as one of its rows.
If you run this code, you should see:
python read_grid.py grid_example.txt
. * . ! . * .
* . ! . ! . *
. * . ! . * .
Grid coordinates
We can think of a grid as a two-dimensional structure that can be accessed using
coordinates (row, column)
:
The cell that is colored blue has coordinates (1, 2)
, meaning it is in row 1,
column 2.
Let’s imagine we have this grid:
grid = [
[1, 2, 3],
[4, 5, 6]
]
If you want to get the value that is in cell coordinate (1, 2)
:
value = grid[1][2]
This says first get row 1, which is [4, 5, 6]
, and then get the item in column
2 of that row, which is 6
. So value = 6
.
If you want to set the value that is in that same cell, then:
grid[1][2] = '*'
This will change the grid so that it looks like this:
1 2 3
4 5 *
The file called coordinates.py
contains a complete example:
def empty_grid(num_rows: int, num_columns: int, value=None) -> list[list]:
"""
Create an empty grid. <num_rows> is the number
of rows in the grid. <num_columns> is the number
of columns in the grid. Fill the grid with None or with
whatever value the caller supplies in <value>.
"""
# create an empty grid
new_grid = []
# keep going until we have created all the rows
while len(new_grid) < num_rows:
new_row = []
# keep going until we have all the columns we need
while len(new_row) < num_columns:
# append an item for this column
new_row.append(value)
# now that we have a full row, append the row
new_grid.append(new_row)
return new_grid
def print_grid(grid: list[list]):
"""
Print all the items in the grid, so that it looks like a grid
"""
# go through all the rows
for row in grid:
for item in row:
# print the item, but with a space after it, not a newline
print(item, end=' ')
print()
def main():
grid = empty_grid(3, 5, value='.')
print_grid(grid)
print()
grid[0][4] = '*'
grid[1][2] = '?'
grid[2][1] = '!'
print_grid(grid)
if __name__ == '__main__':
main()
This code creates an empty grid that has .
in every cell. Then it replaces a
few of these items and prints the grid. You should see this if you run it:
. . . . .
. . . . .
. . . . .
. . . . *
. . ? . .
. ! . . .
Checking values in a grid
Sometimes you will want to check if a grid has a certain value. Here is a function to do that:
def has_value(grid: list[list], row: int, col: int, value: str) -> bool:
"""
Return true if grid[row][col] == value
"""
# be sure the row is in the grid
# if it is not, return False
if row < 0 or row >= len(grid):
return False
# be sure the column is in the grid
# if it is not, return False
if col < 0 or col >= len(grid[row]):
return False
# return True ifthe value at (row, column) is equal to value
return grid[row][col] == value
Notice that we can first check to be sure we have been asked for a row and column that is in the grid. In other words, if the grid is 3 rows and 2 columns, we can’t check for a value in row 10 or column 5.
The number of rows in the grid is len(grid)
. The number of columns in a row is
len(grid[row])
.
To see this at work, here is a complete program, which you can find in
has_value.py
:
def empty_grid(num_rows: int, num_columns: int, value=None) -> list[list]:
"""
Create an empty grid. <num_rows> is the number
of rows in the grid. <num_columns> is the number
of columns in the grid. Fill the grid with None or with
whatever value the caller supplies in <value>.
"""
# create an empty grid
new_grid = []
# keep going until we have created all the rows
while len(new_grid) < num_rows:
new_row = []
# keep going until we have all the columns we need
while len(new_row) < num_columns:
# append an item for this column
new_row.append(value)
# now that we have a full row, append the row
new_grid.append(new_row)
return new_grid
def has_value(grid: list[list], row: int, col: int, value: str) -> bool:
"""
Return true if grid[row][col] == value
"""
# be sure the row is in the grid
# if it is not, return False
if row < 0 or row >= len(grid):
return False
# be sure the column is in the grid
# if it is not, return False
if col < 0 or col >= len(grid[row]):
return False
# return True ifthe value at (row, column) is equal to value
return grid[row][col] == value
def create_grid() -> list[list]:
# create an empty grid
grid = empty_grid(3, 5, value='.')
# fill in a few places in the grid
# with some special characters
grid[0][4] = '*'
grid[1][2] = '?'
grid[2][1] = '!'
return grid
def main():
grid = create_grid()
print(has_value(grid, 0, 0, '*'))
print(has_value(grid, -1, 0, '*'))
print(has_value(grid, 1, 2, '?'))
if __name__ == '__main__':
main()
If you run this code, it should print:
False
False
True