Uncategorized

LIFE in Python 3 – Code Review Stack Exchange


The code you posted offers a good example of the benefits that can flow
downstream from a greater investment upfront in conceptual and naming
consistency. As written, the code has two different ways to represent alive or
dead cells, it toggles back and forth between the language of rows/columns and
the language of x/y coordinates, and it switches between field and
field_copy.

When you hit that point in the development of a program, it’s useful to step
back and commit yourself to some consistency. For example:

field : list of rows
row   : list of cells
cell  : either 'x' (alive) or space (dead)

r     : row index
c     : column index

And let’s also start on a solid foundation by putting all code in functions,
adding a tiny bit of flexibility to usage so we can vary the N of generations
on the command line (handy for debugging and testing). In addition, we want
maintain a strict separation between the algorithmic parts of the program and
the parts of the program that deal with printing and presentation. Here’s one
way to start on that path:

import sys

ALIVE = 'x'
DEAD = ' '

INITIAL_FIELD_TEMPLATE =  [
    '                   ',
    '                   ',
    '                   ',
    '                   ',
    ' xxxxx xxxxx xxxxx ',
    '                   ',
    '                   ',
    '                   ',
    '                   ',
]

DEFAULT_GENERATIONS = 10

def main(args):
    # Setup: initial field and N of generations.
    init = [list(row) for row in INITIAL_FIELD_TEMPLATE]
    args.append(DEFAULT_GENERATIONS)
    n_generations = int(args[0])

    # Run Conway: we now have the fields for all generations.
    fields = list(conway(n_generations, init))

    # Analyze, report, whatever.
    for i, f in enumerate(fields):
        s = field_as_str(f)
        print(f'\nGeneration {i}:\n{s}')

def conway(n, field):
    for _ in range(n + 1):
        yield field           # Temporary implementation.

def field_as_str(field):
    return '\n'.join(''.join(row) for row in field)

if __name__ == '__main__':
    main(sys.argv[1:])

Starting on that foundation, the next step is to make conway() do something
interesting — namely, compute the field for the next generation. The new_field()
implementation is easy if we define a couple of range constants.

RNG_R = range(len(INITIAL_FIELD_TEMPLATE))
RNG_C = range(len(INITIAL_FIELD_TEMPLATE[0]))

def new_field(field):
    return [
        [new_cell_value(field, r, c) for c in RNG_C]
        for r in RNG_R
    ]

def new_cell_value(field, r, c):
    return field[r][c]        # Temporary implementation.

And then the next step is to implement a real new_cell_value(), which we know
will lead us to thinking about neighboring cells. In these 2D grid situations,
neighbor logic can often be simplified by expressing the neighbors in relative
(R, C) terms in a simple data structure:

NEIGHBOR_SHIFTS = [
    (-1, -1), (-1, 0), (-1, 1),
    (0,  -1),          (0,  1),
    (1,  -1), (1,  0), (1,  1),
]

def new_cell_value(field, r, c):
    n_living = sum(
        cell == ALIVE
        for cell in neighbor_cells(field, r, c)
    )
    return (
        field[r][c] if n_living == 2 else
        ALIVE if n_living == 3 else
        DEAD
    )

def neighbor_cells(field, r, c):
    return [
        field[r + dr][c + dc]
        for dr, dc in NEIGHBOR_SHIFTS
        if (r + dr) in RNG_R and (c + dc) in RNG_C
    ]

One final note: by adopting a consistent naming convention and by decomposing
the problem into fairly small functions, we can get away with many short
variable names, which lightens the visual weight of the code and helps with
readability. Within small scopes and within a clear context (both are crucial),
short variable names tend to increase readability. Consider
neighbor_cells(): r and c work because our convention is followed
everywhere; RNG_R and RNG_C work because they build on that convention; dr and
dc work partly for the same reason and partly because they have the context of an
explicitly named container, NEIGHBOR_SHIFTS.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *