Today: idiomatic range index loop, reversed, movie example, grid, grid testing, work on one function at a time, demo HW3

## Will CS106A Get Harder Each Week?

Is this course going to get harder and harder each week? Mercifuly, no! We're actually going to settle down a little from here on out.

## 1. Loop Over N Things - Idiomatic

How are the things numbered individually? It's always the zero based scheme: 0, 1, 2, .. 9

The idiomatic way to loop over them by their index numbers is with the for/i/range loop.

```for i in range(10):
# i in here: 0 1 2 .. 9
# e.g. use s[i]
```
"Idiomatic" here meaning a phrase that is used frequently, becomes familiar and easy to use correctly.

## 2. How loop over things backwards?

The Python built-in `reversed()` function: reverses a sequence such as from range(). Splices into the regular for/i/range idiom to go through the same numbers in reverse order:

```for i in reversed(range(10)):
# i in here: 9 8 7 .. 0
```

For more detail, see the guide Python range()

The reversed() function appears in part of homework-3.

Today we will use this "movie" example and exercise: movie.zip

The move-starter.py file is the code with bugs, and movie.py is a copy of that to work on, and movie-solution.py has the correct code.

## Movie Project + Testing Themes

• Goal: we want an animation where letters fly leftwards on a black background
Kind of like The Matrix
• Divide and Conquer - our mantra
• Test each function separately - our other mantra
• Don't debug the animation as it runs
• Debug a tiny, frozen Doctest case
• Huge time saver
• Today: Doctest for a 2d algorithm function

## Grid Utility Code

• We have done lots of 2d algorithms on images
Always with RGB input/output
Nice, but just one corner of 2d algorithms
• Grid - generic 2d facility
• In the grid.py file
• 2d storage of str, int, .. anything
• Reference: Grid Reference

## Grid Functions

• `grid = Grid(4, 3)` - create, all None initially
• Zero based x,y coordinates for every square in the grid:
origin at upper left
x: 0..grid.width - 1
y: 0..grid.height - 1
• `grid.width` - access width or height
• `grid.get(0, 0)` - returns contents at x,y (error if out of bounds)
• `grid.set(0, 0, 'a')` - set at x,y
• `grid.in_bounds(2, 2)` - returns True if x,y is in bounds

## set_edges() (optional)

Implement set_edges(). Or just go down to look at its Doctests.

```def set_edges(grid):
"""
Set all the squares along the left edge (x=0) to 'a'.
Do the same for the right edge.
Return the changed grid.
"""
pass
```

Solution code:

```def set_edges(grid):
"""
Set all the squares along the left edge (x=0) to 'a'.
Do the same for the right edge.
Return the changed grid.
"""
for y in range(grid.height):
grid.set(0, y, 'a')
grid.set(grid.width - 1, y, 'a')
return grid

```

Q: How can we tell if that code works? With our image examples, at least you could look at the output, although that was not a perfect solution either. Really we want to be able to write test for a small case with visible data.

## Grid Literals

There's a syntax to write a grid out in Python code. It's a little funny looking, but it's fine for small grids. Suppose we have this 3 by 2 grid

Here is the nested-list "literal" representation of that grid:

```[[None, None, 'a'], [None, None, 'b']]
```

The grid is shown as a series of rows, row-0, row-1, .... Each row within square brackets. The special value `None` is in the empty squares. So the first thing you see is the top row, Then the next row and so on.

## Grid Literal Construction

The Grid code understands how to build a grid from a literal. This lets us create a grid in a particular state in one line. This will be handy for testing!

```grid = Grid.build([[None, None, 'a'], [None, None, 'b']])
```

## Write Test for set_edges()

• Now we can write a test for set_edges()
• Literal format used to create and check grid values
• Can set a variable within the >>> Doctest, use on later line
`>>> grid = Grid.build([['b', 'b', 'b'], ['x', 'x', 'x']])`
• Now by typing ctrl-shift-r .. we can test this function in isolation

Here's a visualization - before and after - of grid and how set_edges() modifies it.

Here are the key 3 lines added to set_edges() that make the Doctest: (1) build a "before" grid, (2) call fn with it, (3) write out the expected result of the function call

```    ...
>>> grid = Grid.build([['b', 'b', 'b'], ['x', 'x', 'x']])
>>> set_edges(grid)
[['a', 'b', 'a'], ['a', 'x', 'a']]
...
```

## Run Doctest in PyCharm

• Look at set_edges() in PyCharm
• Select the ">>>" Doctest
Right click it .. Run Doctest
• Maybe have to click a 2nd time for PyCharm to recognize the test
• If the Run Doctest is not present in the menu
You may need to close PyCharm and open the "movie" folder using the PyCharm open... menu
• With the set_edges() code correct, the test should pass
• (optional) Can try putting in a bug

## Random Numbers - Random Module

"Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin." John von Neumann

A computer program is "deterministic" - each time you run the lines with the same input, they do exactly the same thing. Creating random numbers is a challenge, so we settle for "pseudo-random" which are statistically random looking, but in fact are generated by an algorithm that selects the series of numbers.

• A "module" is a library of code we want to use
• Python has many built-in modules containing useful functions
• More module information later in the quarter
• Here the "random" module
• `import random` - this line once at the top of your file
• `random.randrange(n)` - returns 0..n-1 at random
• `random.choice('string')` - returns 1 char at random

Try the "Python Console" tab at the lower-left of your PyCharm window to get an interpreter. It may have a prompt like "In[2]:", but it's basically the same as the old >>> interpreter.

```>>> import random   # starter code has this already
>>>
>>> random.randrange(10)
1
>>> random.randrange(10)
3
>>> random.randrange(10)
9
>>> random.randrange(10)
1
>>> random.randrange(10)
8
>>> random.choice('doofus')
'o'
>>> random.choice('doofus')
'u'
>>> random.choice('doofus')
'o'
>>> random.choice('doofus')
'o'
>>> random.choice('doofus')
's'
>>> random.choice('doofus')
's'
>>> random.choice('doofus')
'o'
>>> random.choice('doofus')
's'
```

## random_right() Function

The code for this one is provided to fill in letters at the right edge. We're not testing this one - testing random behavior is a pain, although it is possible.

```def random_right(grid):
"""
Set the right edge of the grid to some
random letters from 'doofus'.
(provided)
"""
for y in range(grid.height):
if random.randrange(10) == 0:
char = random.choice('doofus')
grid.set(grid.width - 1, y, char)
return grid
```

## scroll_left(grid)

• Real algorithmic code of this project
• Kind of tricky
• The tests are going to save us here
• First sketch out the idea without code
• Then we'll work out the code for it

## Scroll Ideas

• For every x,y
• Want to "move" the 'd' or whatever one to the left, e.g. x-1
• Don't move the `None `if not needed
• Do not move something to x = -1
That's out of bounds!

Before:

```NNN
NdN
```

After:

```NNN
dNN
```
• Write some code in scroll_left() - version 1
• Move square like 'd' to x-1, but only if x > 0
• Version 1 shown below
• Has some bugs
• Run movie.py to see it animate
• Fun to watch, but not good for debugging

## scroll_left() v1 with bugs

```def scroll_left(grid):
"""
Implement scroll_left as in lecture notes.
"""
# v1 - has bugs
for y in range(grid.height):
for x in range(grid.width):
# Move letter at x,y leftwards
val = grid.get(x, y)
if grid.in_bounds(x - 1, y) and val != None:
grid.set(x - 1, y, val)
return grid
```
• Run this in the full GUI. It's bug, but at least it's funny.
• Observe: small bugs can create big output effects
Your output may be totally haywire
But the bug may just be a -1 somewhere
• Running the whole program .. not a good way to debug
• Want: small, isolated, frozen, visible case to look at

We'll use this as the "before" case:

```[['a', 'b', 'c'], ['d', None, None]]
```

What should the after/result look like for that case? If you are writing the code for something, a good first step is writing out an example "after" case for it. Thinking through before/after pair clearly is a good first step, getting your coding ideas organized.

After:

```[['b', 'c', None], [None, None, None]]
```

Strategy aside: if you need to write an algorithm and are staring at a blank screen. Write out a couple before/after cases to get your thoughts started. It's easier to write tests than the code.

## scroll_left() With Doctests

Use this to iterate on the code, get it perfect. The Doctest here is just the one spelled out above.

```def scroll_left(grid):
"""
Implement scroll_left as in lecture notes.
>>> grid = Grid.build([['a', 'b', 'c'], ['d', None, None]])
>>> scroll_left(grid)
[['b', 'c', None], [None, None, None]]
"""
```

## Work on scroll_left()

How do you debug a function? Run its small, frozen Doctests, look at the "got"

• Bug has to do with blanking out a square copying from
• Also dealing with x=0
• After running test once, use ctrl-shift-r (on the mac anyway)
re-run most recent test .. super common case
• Look at the got data from the Doctest
• Then look at your code .. often that's enough
• Fix the code in lecture
1. grid.set(x, y, None) within the if
2. Move it outside the if
• Instead of in_bounds(), checking `x > 0` would work
Since we are only worried about going off the left edge

## scroll_left() Solution

Here is the code with bugs fixed.

```def scroll_left(grid):
"""
Implement scroll_left as in lecture notes.
>>> grid = Grid.build([['a', 'b', 'c'], ['d', None, None]])
>>> scroll_left(grid)
[['b', 'c', None], [None, None, None]]
"""
for y in range(grid.height):
for x in range(grid.width):
# Move letter at x,y leftwards
val = grid.get(x, y)
if grid.in_bounds(x - 1, y) and val != None:
grid.set(x - 1, y, val)
grid.set(x, y, None)
return grid
```

## Run Movie

• Then run the movie program again
• With latest movie.zip .. can specify width/height numbers
```\$ python3 movie.py
\$
\$ python3 movie.py 80 40  # bigger window
```

## Testing - Strategy Conclusions

• Run whole program, see a bug
• Hard to debug with all the code at once
• Write a test per function
• Test case:
• 1. Small
• 2. Isolated
• 3. Frozen
• 4. Visible input/output
Look at "got" .. if you remember one thing
• This is the easiest way to get code working
• We debugged on scroll_left() with its 3x2 case
• Fixed using the small case, the whole big program worked perfectly

## Demo: HW3 Sand Program

• Watching a demo of the program
looks like a lot of work
• This program is very decomposable
• It's 5 functions
• Each function with its own Doctests
• We provide Doctests to get you started
• This thing is a little fun to play with once done
• Even parents can understand what it does!
• Then try to explain Doctests and grid literals to them!