Assembling a Program
This post originally appeared on the Software Carpentry website.
The seventh episode of our lecture on program design is now online. In this one, we actually assemble a complete version of the program step by step using the pieces designed earlier. The final program still has one nasty bug, though, and I'll award a Software Carpentry mug to the first person who can find it (source code attached to this post). For reference, the first six episodes are listed below, along with another link to this one.
- Introduction
- The Grid
- Aliasing
- Randomness
- Finding Neighbors
- Resolving Ties
- Assembling the Program
#!/usr/bin/env python
'''Invasion Percolation Simulation
usage: invperc.py grid_size value_range random_seed
grid_size: the width/height of the grid
must be a positive odd integer
value_range: number of distinct values in grid
must be a positive integer
values will be selected randomly in 1..value_range
random_seed: random number generation seed
must be a positive integer
'''
import sys, random
FILLED = -1 # Used to mark filled cells.
def fail(msg):
print >> sys.stderr, msg
sys.exit(1)
def create_random_grid(N, Z):
'''Return an NxN grid of random values in 1..Z.
Assumes the RNG has already been seeded.'''
assert N > 0, 'Grid size must be positive'
assert N%2 == 1, 'Grid size must be odd'
grid = []
for x in range(N):
grid.append([])
for y in range(N):
grid[-1].append(random.randint(1, Z))
return grid
def mark_filled(grid, x, y):
'''Mark a grid cell as filled.'''
assert 0 <= x < len(grid), \
'X coordinate out of range (%d vs %d)' % \
(x, len(grid))
assert 0 <= y < len(grid), \
'Y coordinate out of range (%d vs %d)' % \
(y, len(grid))
grid[x][y] = FILLED
def is_candidate(grid, x, y):
'''Is a cell a candidate for filling?'''
N = len(grid)
return (x > 0) and (grid[x-1][y] == FILLED) \
or (x < N-1) and (grid[x+1][y] == FILLED) \
or (y > 0) and (grid[x][y-1] == FILLED) \
or (y < N-1) and (grid[x][y+1] == FILLED)
def find_candidates(grid):
'''Find low-valued neighbor cells.'''
N = len(grid)
min_val = sys.maxint
min_set = set()
for x in range(N):
for y in range(N):
if is_candidate(grid, x, y):
if grid[x][y] == min_val:
min_set.add((x, y))
elif grid[x][y] < min_val:
min_val = grid[x][y]
min_set = set([(x, y)])
return min_set
def fill_grid(grid):
'''Fill an NxN grid until filled region hits boundary.'''
N, num_filled = len(grid), 0
while True:
candidates = find_candidates(grid)
assert candidates, 'No fillable cells found!'
x, y = random.choice(list(candidates))
mark_filled(grid, x, y)
num_filled += 1
if x in (0, N-1) or y in (0, N-1):
break
return num_filled
# Main driver.
if __name__ == '__main__':
# Get parameters from command line.
arguments = sys.argv[1:]
try:
grid_size = int(arguments[0])
value_range = int(arguments[1])
random_seed = int(arguments[2])
except IndexError:
fail('Expected 3 arguments, got %d' % len(arguments))
except ValueError:
fail('Expected integer arguments, got %s' % str(arguments))
# Run simulation.
random.seed(random_seed)
grid = create_random_grid(grid_size, value_range)
mark_filled(grid, grid_size/2, grid_size/2)
num_filled_cells = fill_grid(grid) + 1
print '%d cells filled' % num_filled_cells