← Back to course
Python 11-12 / Week 08 / Tuesday
2/6
Week 08 · Searching

Tuesday

Search the grid
// Linear search checks each item; binary search halves sorted data
⏱ about 30 min

Tuesday: Search the Grid

Comet wants FieldSim to point at the first rock before Pip rolls out. "Easy," she says. "The rock in the top right corner."

Wren taps the field drawing. "What does the evidence say? There are four rocks. Which one is first depends on how you read the grid."

"I read like a book," Comet says. "Left to right, top to bottom."

Nova lights up the field one row at a time, each row sweeping from left to right. "How can I help?" she asks. "That is row by row. Search each row in turn, and give back the first match."

Comet hands you the pencil. "Lead programmer, which rock comes first?"

Linear search in a grid

To search a grid, take each row in turn and search along it. The function find_in_grid() gives back the (row, col) of the first match, or None.

FIELD is a list of strings. rows[r][c] reads one character, the same way as a cell in a list of lists. Made-up data from the FieldSim story.

FIELD = [
    "..*..#",
    ".#....",
    "...#*.",
    "*.....",
    "..#..*",
]
# grid_find_try.py
from field_data import FIELD


def find_in_grid(rows, mark):
    """(row, col) of the first cell holding mark, row by row, or None."""
    for r in range(len(rows)):
        for c in range(len(rows[r])):
            if rows[r][c] == mark:
                return (r, c)
    return None


print(find_in_grid(FIELD, "#"))
print(find_in_grid(FIELD, "*"))
print(find_in_grid(FIELD, "R"))
PREDICT GRID_FIND_TRY.PY
  • Read the question.
  • Tap your answer.
Where is the first "#", row by row?
Where is the first "*"?
There is no "R" in FIELD. What comes back?
(0, 5)
(0, 2)
None

Count the checks

Wren makes a copy of find_in_grid that also counts how many cells it checks. The copy has its own file name, so search work never changes the real function.

# grid_count.py
from field_data import FIELD


def find_count(rows, mark):
    """Gives back ((row, col) or None, number of checks)."""
    checks = 0
    for r in range(len(rows)):
        for c in range(len(rows[r])):
            checks += 1
            if rows[r][c] == mark:
                return (r, c), checks
    return None, checks


print(find_count(FIELD, "#"))
print(find_count(FIELD, "*"))
print(find_count(FIELD, "R"))
SearchCells checked, in orderResult
"#"(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)(0, 5)
"*"(0, 0), (0, 1), (0, 2)(0, 2)
"R"all 30 cells, row by rowNone
READ GRID_COUNT.PY
  • Read the question.
  • Tap your answer.
How many checks to find the first "#"?
How many checks when the mark is missing?
((0, 5), 6)
((0, 2), 3)
(None, 30)

Order changes the answer

"First" depends on the search order. This copy searches column by column, top to bottom in each column.

# col_first.py
from field_data import FIELD


def find_by_column(rows, mark):
    """(row, col) of the first cell holding mark, column by column."""
    for c in range(len(rows[0])):
        for r in range(len(rows)):
            if rows[r][c] == mark:
                return (r, c)
    return None


print(find_by_column(FIELD, "#"))
print(find_by_column(FIELD, "*"))
PREDICT COL_FIRST.PY
  • Read the question.
  • Tap your answer.
Column by column, which "*" comes first?
(1, 1)
(3, 0)
At your computer
1. Type grid_find_try.py, save it in your fieldsim folder and run it.
2. Change one thing: search for "." and predict the answer before you run.
3. Break it on purpose: add row, col = find_in_grid(FIELD, "R") at the end. Run it and read the last line of the traceback calmly. The short file below shows the same error.
4. Fix it: put the result in one name, and check it with if spot is not None: before you use it.
# grid_oops.py
from field_data import FIELD


def find_in_grid(rows, mark):
    """(row, col) of the first cell holding mark, row by row, or None."""
    for r in range(len(rows)):
        for c in range(len(rows[r])):
            if rows[r][c] == mark:
                return (r, c)
    return None


row, col = find_in_grid(FIELD, "R")
Traceback (most recent call last):
  File "grid_oops.py", line 14, in <module>
    row, col = find_in_grid(FIELD, "R")
    ^^^^^^^^
TypeError: cannot unpack non-iterable NoneType object
Which kind of error does row, col = None give?
WHY THIS EXERCISEA TypeError means an operation got the wrong type of value. None cannot be split into a row and a column.

You searched a whole field one cell at a time. Tomorrow you put every search into one module, search.py.

← Monday