Today: many basic examples, observations about loops, top-down decomposition

Tricky Case: Double Move

Here is new problem in the "bit if" section - double move. The idea here is that bit paints the 2nd, 4th, etc. moved-to squares red, leaving the others white. This requires two moves within the loop.


alt: double move output

> double-move (while + two moves in loop)

The code below is a good first try, but it generates a move error for certain world widths. Why? The first move is safe, but the second will make an error if the world is even width. Add an if-statement, so the second move and paint are only done when it is legal.

    while bit.front_clear():
        bit.move()
        bit.move()
        bit.paint('red')

Usually you run your code one case at a time, using the Run All option as a final check that all the cases work. In this case, Run All reveals that some cases have a problem with the above code.

The Falling Water problem in the puzzle section also demonstrates this issue.

> falling-water

Decomposition - Program and Functions

"Divide and Conquer" - a classic strategy, works very well with computer code

Program Made of Functions

Our big picture - program made up of functions

alt: program is made of functions, each with def

Decomposition Strategy - Divide and Conquer

Big Picture - Browser Example

Functions - The Wizard of EarthSea

In the Wizard of EarthSea novels by Ursula Le Guin .. each thing in the world has its secret, true name. A magician calls a thing's true name, invoking that thing's power. Strangely, function calls work just like this - a function has a name and you call the function using its name, invoking its power.

How To Call a Function

We've seen def many times - defines a function. Recall that a function has a name and body lines of code, like this "go_west" function:

def go_west(bit):
    bit.left()
    bit.paint('blue')
    ...

To "call" a function means to go run its code, and there are two common ways it is done in Python. Which way a function is called is set by its author when the function is defined.

1. Call by noun.verb

For "object oriented" code, which is how bit is built, the function call is the noun.verb form, e.g. bit.left(). Here "left" is the name of the function. Your code will call bit functions like this, but your own functions will use the more common "def" form below.

2. Call by name

For a function with a regular def, like this:

def go_west(bit):
    bit.left()
    bit.paint('blue')
    ...

The function call is formed by the function's name with parenthesis after it, like this:

    ...
    go_west(bit)
    ...

What Does Function Call Do?

Say the program is running through some lines, which we'll call the "caller" lines. Then the run gets to the following function call line; what happens?

    # caller lines
    bit.right()
    go_west(bit)  # what does this do?
    bit.left()
    ...

What the go_west(bit) directs the computer to do: (a) go run the lines inside the "go_west" function, suspend running here in the caller. (b) When go_west() is done, return to this exact spot in the caller lines and continue running with the next line. In other words, the program runs one function at a time, and function-call jumps over to run that function.


Decomposition 1 - Fill World Blue

This example demonstrates bit code combined with divide-and-conquer decomposition. The program is decomposed into two functions.

> Fill Example

The whole program does this: bit starts at the upper left facing down. We want to fill the whole world with blue, like this

Program Before:
alt: world without blue, bit at upper left

Program After:
alt: world filled blue, bit at lower left

1. - Decompose fill_row_blue() "helper" function

First we'll decompose out a fill_row_blue() function that just does 1 row.

This is a "helper" function - solves a smaller sub-problem.

fill_row_blue() Before (pre)
bit at left facing south

fill_row_blue After (post):
row filled with blue, bit back at start position

We could have you write the code for this one, but we're providing it today to get to the next part.

Run the fill_row_blue a few times, see what it does. (Case-1 in the menu is the fill_row_blue test case).

Function Pre/Post Conditions

Pre/Post - Why Do I Care?

Look at fill_row_blue() Function

def fill_row_blue(bit):
    bit.left()
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
    bit.right()
    bit.right()
    while bit.front_clear():
        bit.move()
    bit.left()
-Now comes the key, magic step for today. First, know what fill_row_blue() does exactly, pre/post.

Challenge: Write fill_world_blue()

2 Row Milestone

As an experiment write code to just solve the first 2 rows, without a loop. This is a test "milestone", working out that some things work, but without solving the whole thing.

1. How can I get the 1st row filled?

This can be done with 1 line of code. Think function-call.

2. How can I get the 2nd line filled?

Where is bit and with what facing after the code above?

fill_world_blue() Functions - A Great Deal!


Decomposition 2: Cover

> Cover Example

Bit starts next to a block of solid squares (these are not "clear" for moving). Bit goes around the 4 sides clockwise, painting everything green.

Cover Before (pre):
world without blue cover done

cover_side() Helper After (post):
alt: after one cover_side

cover_square() After (post):
world filled blue done

1. Run cover_side()

cover_side(bit): Move bit forward until the right side is clear. Color every square green. (demonstrates not left-clear test).

Run this code with case-1, see what it does. Study the code to see how it works, think about its pre/post (make a little drawing).

2. Challenge: write code for cover_square()

cover_square(bit): Bit begins atop the upper left corner, facing right. Paint all 4 sides green. End to the left of the original position, facing up.

Add code to paint the top and right sides. Key ideas:


Top-Down Decomposition Strategy


Decomposition 3: Hurdles Example

> Hurdles

Before
alt: before bit runs series of hurdles

After
alt: after bit runs series of hurdles

Top-Down Decomposition - Hurdles

solve_hurdles()

def solve_hurdles(filename):
    """
    Solve the sequence of hurdles.
    Start facing north at the first hurdle.
    Finish facing north at the end.
    (provided)
    """
    while bit.front_clear():
        solve_1_hurdle(bit)

solve_1_hurdle()

Helper function - solve one hurdle. If we had this, solve_hurdles would be easy. Just doing one hurdle is not so intimidating. Divide and conquer!


alt: solve 1 hurdle

What helper functions would be useful here? Make observation about the 4 moves that make up a hurdle.

Here is a sketch, working out that there are two sub-problem types
alt: A and B sub-problems

Here is the solve_1_hurdle() code - sketching that we have go_wall_right() and go_until_blocked() helpers. Can write this before we write the helpers. Think about pre/post for each.

def solve_1_hurdle(bit):
    """
    Solve one hurdle, painting all squares green.
    Start facing up at the hurdle's left edge.
    End facing up at the start of the next hurdle.
    """
    go_wall_right(bit)
    bit.right()
    bit.move()
    go_wall_right(bit)
    bit.right()
    go_until_blocked(bit)
    bit.left()
    go_until_blocked(bit)
    bit.left()

Power Move: Delegation and Knowing Things

Write Two More Helpers

Now write two more helpers. Here we see the importance of the pre/post to mesh the functions together. (For lecture demo, may just use prepared versions of these.)

def go_wall_right(bit):
    """
    Move bit forward so long as there
    is a wall to the right. Paint
    all squares green. Leave bit with the
    original facing.
    """
    bit.paint('green')
    while not bit.right_clear():
        bit.move()
        bit.paint('green')


def go_until_blocked(bit):
    """
    Move bit forward until blocked.
    Painting all squares green.
    Leave bit with the original facing.
    """
    bit.paint('green')
    while bit.front_clear():
        bit.move()
        bit.paint('green')

Run It

When all the helpers are built, we can try running it on a few worlds. Switch to a small font so you can see all the code at once, watch the run jump around. Go, Bit go!


Some other points to clean up, if we have time.

Helpers At Top - Convention

There is a convention to put the smallest, helper functions first in a file. The larger functions that call them down below. This is just a habit; Python code will work with the functions in any order. Placing the helpers first does have a kind of logic — looking at solve_1_hurdle(), the functions it uses are defined above it, not after.

solve_1_hurdle() Helpers + Triple Quote

At the top of each function is a description of what the function does within triple-quote marks - we'll start writing these from now on. This is a Python convention known as "Pydoc" for each function. The description is just a summary of the pre/post in words.

Run vs. Helper Tests

Previously we had separate testing for each helper which is ideal, and we will do that again in CS106A. In this case, we just run the whole thing and see if it works without the benefit of helper tests.

Coding Style 1.0 - Tactics

CS106A doe not just teach coding. It has always taught how to write clean code with good style. Your section leader will talk with you about the correctness of code, but also pointers for good style.

All the code we show you will follow PEP8, so just picking up the style tactic that way is the easiest.

Python Guide - click on the Style section, we'll pick out a few things today (for hw1), re-visit it for the rest later.