Build with Python

Loops That Think

On this page

Loops That Think

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

A decision on every turn

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")

Remainders

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")

How many passed?

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.

The best one so far

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

A loop inside a loop

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.

Until something changes

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

The first and last turn

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.

In the lab

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.

Your turn

Now try it yourself

Predict what a program will do, run it, and step through it line by line. The checks tell you when your own code works.

Loading the lab…

In this lab

  1. What is left over?

    This loop prints three numbers on each turn: n, the whole part of n divided by 3, and what is left over. The first six lines of output are shown in the question. Work out the last two before you run it.

  2. Alternating squares

    This program draws count squares in a row, all blue. Change it so that the squares at odd positions (i = 1, 3, 5 and so on) are orange, and the others stay blue.

    Do not change where the squares are. The check also tries a row of five.

  3. How many passed?

    This loop looks at the numbers 1 to 20 and counts the ones that divide by 3. Decide what it prints before you run it.

  4. Best so far

    On each turn this loop works out a value and compares it with the best one it has seen. Trace it in your head or on paper before you run it.

  5. Rows and columns

    One loop is inside another. Decide what the program prints and how many lines there are, before you run it.

  6. Checkerboard

    This program draws a board of cells by cells squares, all the same colour. Turn it into a checkerboard: a square is "black" when row + col is odd, and stays "wheat" when it is even.

    Do not move the squares. The check also tries a smaller board.

  7. When does it stop?

    A while loop keeps going as long as its test is True. Decide how many turns this one takes, and what it prints.

  8. First and last

    Each of these four loops was meant to run exactly 10 times. Two of them are wrong by one. For each, say how many times it really runs.

  9. Brick wall

    Draw a wall of rows rows of bricks.

    • A brick is a rectangle 70 wide and 30 tall, coloured "firebrick".
    • Every row has 9 bricks. In a row, each brick starts 75 to the right of the one before, which leaves a gap of 5.
    • Row 0 is at y = 20, and each row is 35 below the one above.
    • In rows 0, 2, 4 and so on the first brick starts at x = 0. In rows 1, 3, 5 and so on every brick is 35 further left, half a brick, so the first one starts at x = -35.

    Parts of some bricks fall outside the canvas. That is expected: the canvas simply cuts them off. The checks try other numbers of rows.

  10. Bounces

    A ball is dropped from height. After each bounce it rises to 60% of the height it had before, which is that height times 0.6.

    Keep going for as long as the ball rises to 1 or more.

    • For each of those bounces draw a bar as tall as the ball rose: 30 wide, coloured "teal", standing on the line y = 380. The first bar's left edge is at x = 20, and each bar is 40 to the right of the one before.
    • When the bouncing is over, print how many bounces there were.

    Dropped from 100, the ball rises to 60, then 36, then 21.6 and so on. Dropped from 1 it rises only to 0.6, so there are no bounces at all. The checks try several heights.

  11. Tallest tower

    Draw twelve towers and put a flag on the tallest.

    • Tower i (0 to 11) has height 40 + (i * step) % 190. With step = 70 that gives 40, 110, 180, 60 and so on.
    • Each tower is a rectangle 30 wide, coloured "slategray", standing on the line y = 380. Tower i has its left edge at x = 30 + i * 45.
    • The flag is a "red" rectangle 30 wide and 12 tall, sitting directly on top of the tallest tower.

    Draw the flag after the loop that draws the towers, with one line. To do that, keep the tallest height and the position of that tower as the loop goes. The checks try other values of step, so the tallest tower moves.

  12. Triangle of dots

    Draw a triangle of dots with rows rows. The first row has one dot, the second has two, and so on, so the last row has rows dots.

    • A dot is a circle of radius 10, coloured "purple".
    • The first dot of every row is at x = 40. Dots in a row are 30 apart.
    • The first row is at y = 40, and each row is 30 below the one above.

    The checks try other numbers of rows.

  13. The loop that never ends

    This countdown should print start, then each number below it down to 1, then Lift off. As it stands it never gets there.

    Before you run it, say to yourself why it cannot stop. Then run it: the lab will stop it for you and say so. Fix it by adding one line.

  14. Savings goal

    You start with nothing in an account. Every month two things happen, in this order:

    1. you add deposit;
    2. the bank adds 1% interest, which multiplies the balance by 1.01.

    Print how many months it takes for the balance to reach goal. Reaching it exactly counts as reaching it.

    There is no picture in this task. With deposit = 100 and goal = 500 the balance after each month is 101, 203.01, 306.0401, 410.100501 and 515.20150601, so the answer is 5. The checks try other deposits and goals, including one where the balance lands exactly on the goal.