A loop that does exactly the same thing on every turn can draw a row of circles. A loop that makes a decision on every turn can draw a checkerboard, count the things that pass a test, or find the largest value in a run of numbers. This module puts the if from Decisions inside the loop from Repeat.
Before this, you can: trace a for loop and say the value of i on each turn; write an if, elif, else; keep a running total.
By the end you will be able to make a decision on every turn of a loop, use a remainder to pick out every second or third turn, count and find a best value as you go, read a loop inside a loop, and write a while loop that stops when something changes.
Before you read on, decide what this prints:
print(7 / 2, 7 // 2, 7 % 2)
3.5 3 1
If you expected 3 first, you are thinking of a language such as Java, where dividing two whole numbers gives a whole number. In Python / always gives a number with a decimal point, here 3.5. Division that gives a whole number has its own operator, //, and what is left over has another, %. This module uses % in nearly every loop.
Two details, so that neither surprises you later. A number with a decimal point is stored to about sixteen digits, so a long calculation can end a hair away from the value you expect, such as 21.599999999999998 where you expected 21.6; you will meet this in the lab's bouncing ball, and it is not a mistake in your program. And // rounds down, which matters below zero:
print(-7 // 2, -7 % 2)
-4 1
Java would give -3 and -1. In Python the remainder of dividing by a positive number is never negative, which is what makes % so convenient for patterns that repeat.
If you have programmed before, most of this module is familiar ground with a few differences worth noticing:
| The same as in Java | Different in Python |
|---|---|
a loop with an if inside it |
the body is marked by indentation, not braces |
| a counter, and a "largest so far" | there is no ++: write count = count + 1 or count += 1 |
while with a test |
no brackets round the test, and a colon after it |
the remainder operator % |
/ is never whole-number division; // is |
| a loop inside a loop | the conditions use the words and, or, not |
An if inside a loop is tested again on every turn, with that turn's value of the loop variable:
for i in range(6):
if i < 3:
print(i, "small")
else:
print(i, "large")
0 small
1 small
2 small
3 large
4 large
5 large
There are two levels of indentation. The if and the else are indented once, because they belong to the loop. The lines under them are indented twice, because they belong to the if or the else, which belong to the loop.
The same loop can draw. Here the decision chooses a colour:
from scimigo import canvas, circle
canvas(600, 400)
for i in range(6):
if i < 3:
circle(60 + i * 90, 200, 30, color="teal")
else:
circle(60 + i * 90, 200, 30, color="orange")
7 // 3 is 2, because 3 goes into 7 twice, and // keeps only the whole number. 7 % 3 is 1, because 1 is left over. Watch both as a number counts up:
for n in range(8):
print(n, n // 3, n % 3)
0 0 0
1 0 1
2 0 2
3 1 0
4 1 1
5 1 2
6 2 0
7 2 1
Look down the last column: 0, 1, 2, 0, 1, 2, 0, 1. A remainder goes round and round, and it never reaches the number you divide by. That makes % the tool for anything that repeats:
n % 2 is 0 for even numbers and 1 for odd ones;n % 3 == 0 is True for every third number: 0, 3, 6, 9 and so on.Put that in a loop and every second shape can be different:
from scimigo import canvas, rectangle
canvas(600, 400)
for i in range(8):
if i % 2 == 0:
rectangle(40 + i * 65, 170, 55, 55, color="navy")
else:
rectangle(40 + i * 65, 170, 55, 55, color="gold")
To count the turns on which something is true, start a counter at 0 before the loop and add 1 inside an if:
count = 0
for n in range(1, 11):
if n % 3 == 0:
count = count + 1
print(count)
3
The counter changes only on the turns where the test is True, which here are n = 3, 6 and 9. On the other seven turns the if is tested, found False, and nothing happens.
This is the running total from Repeat with one difference: the adding is inside an if, so it does not happen on every turn.
To find the largest value a loop produces, keep the largest one seen so far and replace it whenever a larger one comes along:
best = 0
for i in range(5):
value = (i * 4) % 7
if value > best:
best = value
print(i, value, best)
0 0 0
1 4 4
2 1 4
3 5 5
4 2 5
Read the last column from top to bottom. best never goes down. It changes on the turns where the new value beats it (turns 1 and 3 here) and stays put on the others.
Two things can go wrong. If best = 0 is inside the loop, it is reset on every turn and the loop only remembers the last value. And a starting value of 0 only works if no value can be below it; when values might be negative, start from the first value instead.
Often you want to know where the best one was, as well as how big. Keep a second variable and update both together:
best = 0
best_at = 0
for i in range(5):
value = (i * 4) % 7
if value > best:
best = value
best_at = i
print(best, best_at)
5 3
The body of a loop can contain another loop. The inner loop runs from start to finish on every turn of the outer one:
for row in range(2):
for col in range(3):
print(row, col)
0 0
0 1
0 2
1 0
1 1
1 2
row stays at 0 while col goes 0, 1, 2. Only then does row become 1, and col starts again from 0. Two turns of the outer loop, three of the inner one each time: the print runs 2 × 3 = 6 times.
Rows and columns are what a grid is made of. Use col for x and row for y:
from scimigo import canvas, rectangle
canvas(600, 400)
for row in range(3):
for col in range(5):
rectangle(100 + col * 80, 80 + row * 80, 70, 70, color="teal")
And because row and col are ordinary numbers, a decision can use both. row + col is even on one set of squares and odd on the squares between them, which is the pattern of a checkerboard.
A for loop is for when you know how many turns you need. A while loop is for when you only know when to stop:
height = 80
while height > 10:
print(height)
height = height / 2
print("stopped at", height)
80
40.0
20.0
stopped at 10.0
while is followed by a test and a colon. Python checks the test; if it is True, it runs the indented lines and comes back to check again. As soon as the test is False, it carries on with the line after the loop.
The test is checked before each turn, never in the middle of one. Here height becomes 10.0 at the end of the third turn; then 10.0 > 10 is False, so there is no fourth.
In loops like these, where the test is the only way out, something inside the loop has to change what the test looks at. If nothing does, the test stays True and the loop never ends. The lab stops a program that runs too long and tells you so; when you see that message, look for the line that should have changed the variable in the test.
To count the turns of a while loop, use a counter, as in "How many passed?":
height = 80
turns = 0
while height > 10:
height = height / 2
turns = turns + 1
print(turns)
3
Loops are rarely wrong in the middle. They are wrong at the ends: one turn too many, or one too few. These three look alike and run a different number of times:
a = 0
for i in range(5):
a = a + 1
b = 0
for i in range(1, 5):
b = b + 1
c = 0
n = 0
while n <= 5:
n = n + 1
c = c + 1
print(a, b, c)
5 4 6
range(5) gives 0 to 4: five turns. range(1, 5) gives 1 to 4: four turns. The while runs for n = 0, 1, 2, 3, 4 and 5: six turns, because <= lets 5 through.
When a loop's result is off by one, do not guess. Ask two questions and answer them from the code: what is the variable on the first turn, and what is it on the last turn that actually runs? Stepping through the program in the lab shows you both.
You will work out remainders before running them, colour every second square, count and find the largest value in a loop, read the output of two nested loops, turn a plain grid into a checkerboard, build a brick wall, and count a bouncing ball's bounces with a while loop.