Comet spreads the field map across the bench. "Before Pip rolls, I want to know every cell it could ever reach from the start," she says.
Wren frowns at the rocks. "What does the evidence say? We could check by hand, but that is a lot of cells."
Nova projects the grid and lights up the start cell. "How can I help?" she asks. "From one open cell, Pip can try four neighbors. Each neighbor is a smaller version of the same question."
Comet sketches four arrows. "So the function calls itself four times!"
"And it must remember where it has been," Wren adds. "Or Pip will go back and forth forever."
They both look at you. "Lead programmer, build it."
This week, recursion.py joins your fieldsim folder. Today it gets three functions: countdown, total and reachable.
Tomorrow you will add three more. The field data, made-up data from the FieldSim story, comes from field_data.py.
# recursion.py
# Functions that call themselves (week 10).
def countdown(n):
if n == 0:
print("Go!")
else:
print(n)
countdown(n - 1)
def total(nums):
"""Sum of a list, the recursive way."""
if len(nums) == 0:
return 0
return nums[0] + total(nums[1:])
def reachable(grid, row, col, seen):
"""How many open cells the rover can reach from (row, col)."""
if not grid.is_open(row, col) or (row, col) in seen:
return 0
seen.add((row, col))
return (1 + reachable(grid, row - 1, col, seen)
+ reachable(grid, row + 1, col, seen)
+ reachable(grid, row, col - 1, seen)
+ reachable(grid, row, col + 1, seen)) # reach_demo.py # Try the functions in recursion.py. from field_data import FIELD from grid import FieldGrid from recursion import countdown, total, reachable countdown(2) print(total([42, 18, 65])) grid = FieldGrid(FIELD) print(reachable(grid, 0, 0, set()))
2 1 Go! 125 26
Trace a tiny field next. A tiny 2 by 2 field makes the calls easy to follow. Row 1 starts with a rock.
# tiny_reach.py from grid import FieldGrid from recursion import reachable tiny = FieldGrid(["..", "#."]) print(reachable(tiny, 0, 0, set())) print(reachable(tiny, 1, 0, set()))
3 0
set_cell changes the grid object only. FIELD in field_data.py stays the same, so the grid is your own copy of the field to change.
Add these lines to the end of reach_demo.py. Predict both numbers before you run it.
# reach_demo.py # Try the functions in recursion.py. from field_data import FIELD from grid import FieldGrid from recursion import countdown, total, reachable countdown(2) print(total([42, 18, 65])) grid = FieldGrid(FIELD) print(reachable(grid, 0, 0, set())) grid.set_cell(0, 1, "#") print(reachable(grid, 0, 0, set())) grid.set_cell(1, 0, "#") print(reachable(grid, 0, 0, set())) print(FIELD[0])
2 1 Go! 125 26 25 1 ..*..#
In recursion.py, delete or (row, col) in seen from the first line of reachable, so it reads like this:
def reachable(grid, row, col, seen):
"""How many open cells the rover can reach from (row, col)."""
if not grid.is_open(row, col):
return 0
seen.add((row, col))
return (1 + reachable(grid, row - 1, col, seen)
+ reachable(grid, row + 1, col, seen)
+ reachable(grid, row, col - 1, seen)
+ reachable(grid, row, col + 1, seen)) Run reach_demo.py. The first four lines still print, then a very long traceback ends with this last line: RecursionError: maximum recursion depth exceeded
Put or (row, col) in seen back, save and run again. You should see 26 once more.
You built a recursive tool for the real field map, then broke it and fixed it. That is careful engineering, lead programmer.