BYU logo Computer Science

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:

a grid of numbers, with 1, 2, 3 in the top row and 4, 5, 6 in the bottom row

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 #1Loop #2rowitem
11[1, 2, 3]1
12[1, 2, 3]2
13[1, 2, 3]3
21[4, 5, 6]4
22[4, 5, 6]5
23[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 rows
  • num_columns is the number of columns
  • value 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 # 1Loop #2new_gridnew_row
11[]['*']
12[]['*','*']
13[]['*', '*', '*']
1done[['*', '*', '*']]['*', '*', '*']
21[['*', '*', '*']]['*']
22[['*', '*', '*']]['*','*']
23[['*', '*', '*']]['*', '*', '*']
2done[['*', '*', '*'],['*','*','*']]['*', '*', '*']

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):

grid with 2 rows and 3 columns, shown coordinates at the top and left

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