BYU logo Computer Science

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

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. So we can print out a grid like this:

def print_grid(grid):
    for row in grid:
        for item in row:
            print(item, end=' ')
        print()

If we call it like this:

numbers = [
    [1, 2, 3],
    [4, 5, 6]
]

print_grid(numbers)

then we get:

1 2 3 
4 5 6 

Go back and look at this example code. We are using print(item, end=' ') to print each item in each row. 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 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>

Copying a grid

Let’s write a function that copies a grid:

def copy_grid(grid):
    new_grid = []
    for row in grid:
        new_row = []
        for item in row:
            new_row.append(item)
        new_grid.append(new_row)
    return new_grid

We need to be sure to create a new grid, using the new_grid variable and initializing it to an empty list []. Then, each time we create a new row that is also initialized with []. We append each item in a row to a new row, then append each row to the new grid.

Step through every line of this in the debugger to be sure you understand what it is doing.

If we call this function with:

numbers = [
    [1, 2, 3],
    [4, 5, 6]
]
more_numbers = copy_grid(numbers)
print(more_numbers)

We will see:

[[1, 2, 3], [4, 5, 6]]

Remember, a grid is just a nested set of lists. Each nested list is a row in the grid.

Creating an empty grid

Sometimes you want to create an empty grid, meaning a grid filled with None in every row and column. Here is a function that will do that:

def empty_grid(num_rows, num_columns):
    new_grid = []
    for row in range(num_rows):
        new_row = []
        for column in range(num_columns):
            new_row.append(None)
        new_grid.append(new_row)
    return new_grid

The function takes two parameters — num_rows and num_columns. This tells us how many rows and columns we need to create in the grid.

Like when we copy a grid, above, we create an empty grid, initialized to []. Each time we create a new row, we likewise initialize it to []. We append None to the row, one for each column. We append each row to the new grid.

We can call this function:

print(empty_grid(3, 2))

And this will print an empty grid of 3 rows and 2 columns:

[[None, None], [None, None], [None, None]]

Testing for a jagged grid

A jagged grid is one where the rows are not all the same length.

Let’s write a function that returns True if a grid is jagged. Think about how you might solve this problem. Out of all of the rows of a grid, there might be just one that is too long:

a grid with most rows having 4 items but one row having 5 items

Or there might be many rows that are too long! Or maybe there are none!

We can loop through the grid and store the length of the first row into a variable called size. Then for each subsequent row, we can check if the length of that row is equal to size. If it is not, we can return early with True. Otherwise, after we loop over the entire grid, we can return False.

Here is some code that implements this algorithm:

def is_jagged(grid):
    size = None
    for row in grid:
        if size is None:
            size = len(row)
        elif len(row) != size:
            return True
    return False

Notice that this kind of algorithm is a lot like finding the maximum of a list of numbers. We start with size equal to None. This means that the first time we check a row, we will set size equal to the length of that row. For every subsequent row, we will compare the length of the new row to size.

We can call this function to check for a jagged grid:

grid_a = [
    [1, 2, 3, 4],
    [5, 6, 7]
]
print(is_jagged(grid_a))

grid_b = [
    [1, 2],
    [3, 4],
    [5, 6],
    [7, 8]
]
print(is_jagged(grid_b))

This should print True and then False.

Parsing grids

It will be handy to load a grid from a file. Lets imagine an input file, called grid.txt contains this:

4 5 6 7
8 9 10 11
1 2 3 4

Each row is on a separate line of the file. We have a grid containing only integers. Each integer is separated by spaces. This means we can use split() to get each number in each row, and then append the number to the grid. We have to be sure to convert from strings to integers as we do this.

Here is some code that reads a grid from a file:

def load_grid(filename):
    grid = []
    with open(filename) as file:
        for line in file:
            row = []
            for item in line.strip().split():
                row.append(int(item))
            grid.append(row)
    return grid

We use line.strip().split() to first strip off the newline at the end of the line, and then split the line into a list of strings based on the space separator. We can then append each item to its row with row.append(int(item)), where we are sure to convert to integers first.

Let’s load grid.txt into a grid and then print it:

grid = load_grid('grid.txt')
print(grid)

This should print:

[[4, 5, 6, 7], [8, 9, 10, 11], [1, 2, 3, 4]]

Converting a list to a grid

Sometimes you are given just a long list of data:

['Amy', 'Anna', 'Angela', 'Anthony', 'Angelo', 'Adrian']

and you need to convert it to a grid. let’s write a function that does this. Whoever calls our function specifies the number of rows and the number of columns. We will assume that the number of items in the list fits the dimensions of our grid perfectly.

We need to plan this out to be sure we get it right. Let’s say we want to convert the above list into a grid that has 3 rows and 2 columns. Then:

the list of names above put into a 3 x 2 grid

This means row 0 has items 0 and 1 from the list, row 1 has items 2 and 3, and the row 2 has items 4 and 5. If we are in row i and column j, then we can calculate the item we need to place here as i * num_columns + j. Try out this math and be sure it is right.

If we are in row 1, column 1, then we should store item 1 * 2 + 1 = 3 from the list.

Here is code to do this:

def to_grid(items, num_rows, num_columns):
    grid = []
    for i in range(num_rows):
        row = []
        for j in range(num_columns):
            row.append(items[i * num_columns + j])
        grid.append(row)
    return grid

If we run this:

names = ['Amy', 'Anna', 'Angela', 'Anthony', 'Angelo', 'Adrian']

name_grid = to_grid(names, 3, 2)
print_grid(name_grid)

We get:

Amy Anna 
Angela Anthony 
Angelo Adrian