Exit slides Esc

Unit 6 — What Does This Program Do? (Looping)

Slide 1 of 61

  1. The problem: How far does the ball travel?

    A ball is dropped from a height of h centimetres. Each time it hits the floor it bounces back up to three fifths of the height it fell from, rounded down to a whole centimetre. This ACSL program follows the ball while it is still bouncing at least 10 cm high:

    INPUT h
    bounces = 0
    distance = h
    WHILE h >= 10
       h = int(h * 3 / 5)
       bounces = bounces + 1
       distance = distance + 2 * h
    END WHILE
    OUTPUT bounces, distance
  2. What is output when the input is h = 100?

    This is the Junior What Does This Program Do? question for Contest 2: a short program with a loop, traced by hand with no computer. A loop runs the same few lines again and again, and every pass changes the variables a little. One slip on pass 3 and every pass after it is wrong, so the skill is keeping careful track.

  3. This unit teaches it in three steps: how ACSL writes its two loops and how to turn them into Python (Lesson 1), how to trace a loop by hand with a table, including counters, totals and a running maximum (Lesson 2), and how to trace one loop inside another (Lesson 3). Write down your first guess now. You will check it in Lesson 1 and work it out by hand in Lesson 2.

    • Translate ACSL FOR … TO … STEP loops into Python range, including loops that count down.

    • Count a loop's passes before tracing it, and spot loops that never start.

    • Trace WHILE loops, counters, accumulators and a running maximum by hand.

    • Trace loops inside loops whose inner bounds depend on the outer counter.

  4. Lesson 1 — Loops in ACSL pseudocode

    Division: Junior and above.

    ACSL pseudocode has two loops.

    • FOR k = start TO end STEP s … NEXT k counts k from start to end, adding s each time.

    • WHILE condition … END WHILE repeats while the condition is true.

    Everything else is the dialect you learned in Unit 3: % is the remainder, abs(x) drops the sign, and int(x) rounds down. In this unit every value is a whole number, and a division always appears inside int( … ).

  5. FOR includes the end value

    FOR k = 4 TO 16 STEP 4
       OUTPUT k
    NEXT k

    This outputs 4, 8, 12 and 16: ACSL's FOR includes its end value. Python's range stops before its end, so the translation needs an end one step further on: range(4, 17, 4).

  6. for k in range(4, 17, 4):
        print(k)

    Output

    4
    8
    12
    16
    

    16 is printed. With range(4, 16, 4) it would not be, and the loop would run one pass short. When there is no STEP, the step is 1: FOR k = 1 TO 5 is range(1, 6).

  7. Counting down with a negative STEP

    A negative STEP counts down. FOR k = 10 TO 1 STEP -3 visits 10, 7, 4 and 1. Counting down, Python's end must be one below ACSL's end, so the translation is range(10, 0, -3).

  8. total = 0
    for k in range(10, 0, -3):
        total = total + k
    print(total)

    Output

    22
    

    10 + 7 + 4 + 1 is 22. The rule for translating: go one past the ACSL end in the direction of travel. Add 1 when counting up; subtract 1 when counting down.

  9. When the end is not hit exactly

    The counter does not have to land on the end value. FOR k = 3 TO 14 STEP 4 visits 3, 7 and 11. The next value, 15, is past 14, so the loop stops there.

  10. for k in range(3, 15, 4):
        print(k)

    Output

    3
    7
    11
    

    The last pass is the last value that is not past the end. Before tracing a stepped loop, write out the whole list of counter values.

  11. A loop that never starts

    If the start is already past the end, the body runs zero times. FOR k = 5 TO 2 counts up from 5, but 5 is already past 2.

  12. count = 0
    for k in range(5, 3):
        count = count + 1
    print(count)

    Output

    0
    

    count is still 0. The same happens with a negative STEP when the start is below the end: FOR k = 1 TO 8 STEP -2 never runs.

  13. How many passes?

    When a FOR loop does run, the number of passes is

    int((end - start) / step) + 1

    For FOR k = 3 TO 14 STEP 4 that is int(11 / 4) + 1, which is 2 + 1 = 3. Python's len gives the number of values in a range, so you can check the formula.

  14. print(len(range(3, 15, 4)), (14 - 3) // 4 + 1)
    print(len(range(10, 0, -3)), (1 - 10) // -3 + 1)

    Output

    3 3
    4 4
    

    Both lines agree: 3 passes, then 4 passes. Counting the passes first tells you how many rows your trace table will need.

  15. WHILE tests at the top

    WHILE condition
       …
    END WHILE

    The condition is tested before each pass. If it is false the very first time, the body runs zero times. It is not tested in the middle of the body, so a variable can go past the limit during the last pass.

  16. x = 1
    while x < 50:
        x = x * 3
    print(x)

    Output

    81
    

    x goes 1, 3, 9, 27, 81. When x is 27 the test 27 < 50 is true, so the body runs once more and x becomes 81. Then 81 < 50 is false and the loop ends. The output is 81, not a number below 50.

  17. ACSL int(a / b) is Python's //

    When a and b are whole numbers, ACSL's int(a / b) is exactly Python's a // b. Both round down, even below zero. math.floor(a / b), from Unit 3, gives the same answer; Python's own int(a / b) does not.

  18. import math
    
    print(17 // 5, math.floor(17 / 5))
    print(-17 // 5, math.floor(-17 / 5), int(-17 / 5))

    Output

    3 3
    -4 -4 -3
    

    -17 / 5 is -3.4, and rounding down gives -4. // and math.floor both give -4; Python's int chops toward zero and gives -3. So translate ACSL int(a / b) as a // b. In this unit's questions, % is only used on numbers that are not negative.

  19. Translating the opening problem

    Here is the bouncing-ball program, translated line by line. The input value is typed in, since nobody is here to type it.

  20. h = 100
    bounces = 0
    distance = h
    while h >= 10:
        h = h * 3 // 5
        bounces = bounces + 1
        distance = distance + 2 * h
    print(bounces, distance)

    Output

    5 372
    
  21. WHILE … END WHILE became while with an indented body, and int(h * 3 / 5) became h * 3 // 5. Each bounce adds 2 * h: the ball goes up h and comes back down h. Did it match your guess? Running the translation is how you check an answer while you practise; in the contest, Lesson 2's trace table is how you get it.

  22. The full program, reading h from the input, is saved as assets/l1.py.

  23. Try it
    h = int(input())
    bounces = 0
    distance = h
    while h >= 10:
        h = h * 3 // 5
        bounces = bounces + 1
        distance = distance + 2 * h
    print(bounces, distance)
  24. Run it from this unit's folder, feeding it an input file:

    python assets/l1.py < assets/l1/1.in

    assets/l1/2.in drops the ball from 9 cm. Predict what happens before you run it: how many times does the WHILE body run?

  25. Lesson 2 — Tracing loops by hand

    Division: Junior and above.

    To trace a loop, keep a table with one column per variable and one row per pass. Before a FOR loop, write down the list of counter values. Before each pass of a WHILE loop, work out the test and write true or false. Fill each row from left to right, using the values in the row above.

  26. Tracing the opening problem

    Input: h = 100, so bounces = 0 and distance = 100 before the loop.

    pass test h >= 10 h bounces distance note
    1 100 >= 10 true 60 1 220 300 / 5 = 60
    2 60 >= 10 true 36 2 292 180 / 5 = 36
    3 36 >= 10 true 21 3 334 108 / 5 = 21.6, down to 21
    4 21 >= 10 true 12 4 358 63 / 5 = 12.6, down to 12
    5 12 >= 10 true 7 5 372 36 / 5 = 7.2, down to 7
    – 7 >= 10 false the loop ends

    The output is 5 372. Check it against assets/l1/1.out.

  27. Counters and accumulators

    A counter goes up by 1 each time something happens. An accumulator (a running total) adds a value each time. Both start before the loop, usually at 0.

    total = 0
    count = 0
    FOR k = 3 TO 30 STEP 5
       IF k % 2 == 0 THEN
          total = total + k
       ELSE
          count = count + 1
       END IF
    NEXT k
    OUTPUT total, count
  28. total = 0
    count = 0
    for k in range(3, 31, 5):
        if k % 2 == 0:
            total = total + k
        else:
            count = count + 1
    print(total, count)

    Output

    54 3
    

    The counter values are 3, 8, 13, 18, 23, 28. The even ones, 8 + 18 + 28, make total 54, and the three odd ones make count 3. In a trace table, only one of the two columns changes on each row.

  29. A running maximum

    A running maximum remembers the largest value seen so far, and often where it was seen. Each pass compares the new value with the best so far and replaces it only when the new one is larger.

  30. best = 0
    where = 0
    for k in range(1, 7):
        v = k * 5 % 13
        if v > best:
            best = v
            where = k
    print(best, where)

    Output

    12 5
    

    v goes 5, 10, 2, 7, 12, 4, so best goes 5, 10, 10, 10, 12, 12, and the answer is 12 5. Starting best at 0 is safe here only because v is never negative.

  31. Watch the comparison when two values tie. With >, a tie does not replace the best, so the first one is kept. With >=, a tie does replace it, so the last one is kept.

  32. first = 0
    last = 0
    best1 = -1
    best2 = -1
    for k in range(1, 6):
        v = k * 4 % 6
        if v > best1:
            best1 = v
            first = k
        if v >= best2:
            best2 = v
            last = k
    print(first, last)

    Output

    1 4
    

    v goes 4, 2, 0, 4, 2. The largest value, 4, appears at k = 1 and again at k = 4. > keeps 1; >= moves on to 4. Contest questions often hang on exactly this one character.

  33. Taking a number apart, digit by digit

    n % 10 is the last digit of n, and int(n / 10) removes it. A WHILE loop that repeats both, until n is 0, visits every digit from right to left.

    n = 5083
    s = 0
    big = 0
    WHILE n > 0
       d = n % 10
       s = s + d
       IF d > big THEN
          big = d
       END IF
       n = int(n / 10)
    END WHILE
    OUTPUT s, big
  34. n = 5083
    s = 0
    big = 0
    while n > 0:
        d = n % 10
        s = s + d
        if d > big:
            big = d
        n = n // 10
    print(s, big)

    Output

    16 8
    

    n goes 5083, 508, 50, 5, 0, and d takes the digits 3, 8, 0, 5. The output is 16 8. The loop runs once per digit, which is why WHILE fits here: the program does not know in advance how many digits there are.

  35. abs inside a loop

    abs turns a difference into a distance, which can never be negative. Here a marker jumps along a number line, and the program adds up how far it travels.

  36. pos = 0
    dist = 0
    for k in range(1, 7):
        new = k * k % 7 - 3
        dist = dist + abs(new - pos)
        pos = new
    print(dist)

    Output

    12
    

    The marker visits -2, 1, -1, -1, 1, -2, starting from 0. The jumps are 2, 3, 2, 0, 2, 3, all counted as positive, so the output is 12. Without abs, the jumps to the left would cancel the jumps to the right.

  37. Rounding down below zero, again and again

    Inside a loop, a mistake with int is repeated on every pass. Compare ACSL's int (//) with Python's int, which chops toward zero.

  38. x = 9
    y = 9
    for k in range(4):
        x = x // 2 - 3
        y = int(y / 2) - 3
        print(x, y)

    Output

    1 1
    -3 -3
    -5 -4
    -6 -5
    

    The two agree while the numbers are positive. At -3, ACSL's int(-3 / 2) is int(-1.5), which is -2, but Python's int gives -1. From then on every pass starts from a different value, and the last line is -6 -5. The ACSL answer is -6.

  39. Put it together: this program reads one whole number, which may be negative but is not 0, and prints how many digits it has, their sum, and the largest digit. It uses abs, a counter, an accumulator and a running maximum in one WHILE loop. It is saved as assets/l2.py.

  40. Try it
    n = abs(int(input()))
    digits = 0
    total = 0
    largest = 0
    while n > 0:
        d = n % 10
        digits = digits + 1
        total = total + d
        if d > largest:
            largest = d
        n = n // 10
    print(digits, total, largest)
  41. Trace it by hand on each input before you run it from this unit's folder:

    python assets/l2.py < assets/l2/1.in
  42. abs comes first, so -4096 is treated as 4096 (assets/l2/2.in). The input 0 is not allowed: WHILE 0 > 0 is false at once, so the program would say that 0 has no digits at all.

  43. Lesson 3 — Loops inside loops

    Division: Junior and above.

    A loop can sit inside another loop. The inner loop runs all the way through on every pass of the outer loop. In ACSL questions the inner loop's start or end often uses the outer counter, so the inner loop runs a different number of times on each outer pass.

  44. An inner loop that grows

    c = 0
    FOR i = 1 TO 4
       FOR j = 1 TO i
          c = c + 1
       NEXT j
    NEXT i
    OUTPUT c

    Make one row per outer pass and write out the inner values:

    i j takes inner passes
    1 1 1
    2 1, 2 2
    3 1, 2, 3 3
    4 1, 2, 3, 4 4

    So c is 1 + 2 + 3 + 4, which is 10.

  45. c = 0
    for i in range(1, 5):
        for j in range(1, i + 1):
            c = c + 1
    print(c)

    Output

    10
    

    The inner end i becomes i + 1 in Python, by the same "one past the end" rule.

  46. An inner loop that shrinks

    If the inner loop starts at the outer counter, FOR j = i TO 4, it gets shorter each time: 4 passes, then 3, 2 and 1. Each (i, j) pair has i <= j, so every pair of numbers from 1 to 4 is visited once, with doubles such as (2, 2) included.

  47. c = 0
    for i in range(1, 5):
        for j in range(i, 5):
            c = c + 1
            print(i, j)
    print(c)

    Output

    1 1
    1 2
    1 3
    1 4
    2 2
    2 3
    2 4
    3 3
    3 4
    4 4
    10
    

    (1, 1) to (1, 4) come first, then (2, 2) to (2, 4), and so on down to (4, 4). c is 4 + 3 + 2 + 1, which is 10 again.

  48. An inner loop that sometimes never starts

    c = 0
    FOR i = 1 TO 5
       FOR j = 3 TO i
          c = c + 1
       NEXT j
    NEXT i
    OUTPUT c

    When i is 1 or 2, the inner start 3 is already past the end, so the inner loop runs zero times.

  49. c = 0
    for i in range(1, 6):
        for j in range(3, i + 1):
            c = c + 1
    print(c)

    Output

    6
    

    The inner passes are 0, 0, 1, 2, 3, so c is 6. When you make the table, write none in the rows where the inner loop does not run, so you do not invent passes.

  50. A negative STEP outside, int inside

    t = 0
    FOR i = 6 TO 1 STEP -2
       FOR j = 1 TO int(i / 2)
          t = t + j
       NEXT j
    NEXT i
    OUTPUT t

    The outer counter takes 6, 4, 2. The inner end is int(i / 2): 3, then 2, then 1.

  51. t = 0
    for i in range(6, 0, -2):
        for j in range(1, i // 2 + 1):
            t = t + j
    print(t)

    Output

    10
    

    The three outer passes add 1 + 2 + 3, then 1 + 2, then 1, which makes 10. Work out the inner end once per outer pass and write it at the start of the row.

  52. A WHILE loop inside a FOR loop

    The inner loop can be a WHILE. Here it takes each k apart into its digits, as in Lesson 2. The variables the inner loop uses must be reset at the top of every outer pass.

  53. count = 0
    for k in range(19, 26, 3):
        m = k
        s = 0
        while m > 0:
            s = s + m % 10
            m = m // 10
        print(k, s)
        if s > 5:
            count = count + 1
    print(count)

    Output

    19 10
    22 4
    25 7
    2
    

    k takes 19, 22, 25, with digit sums 10, 4, 7, so count is 2. If s = 0 were written before the FOR loop instead, the digit sums would pile up: 10, then 14, then 21.

  54. Put it together: a number's proper divisors are the numbers smaller than it that divide it exactly: for 12 they are 1, 2, 3, 4, 6, which add up to 16. This program reads n and finds which number from 1 to n has the largest sum of proper divisors, printing that number and the sum. If two numbers tie, the smaller one wins.

  55. INPUT n
    best_k = 1
    best_sum = 0
    FOR k = 1 TO n
       s = 0
       FOR j = 1 TO int(k / 2)
          IF k % j == 0 THEN
             s = s + j
          END IF
       NEXT j
       IF s > best_sum THEN
          best_k = k
          best_sum = s
       END IF
    NEXT k
    OUTPUT best_k, best_sum

    The inner loop stops at int(k / 2), because no proper divisor of k is bigger than half of k. It is saved as assets/l3.py.

  56. Try it
    n = int(input())
    best_k = 1
    best_sum = 0
    for k in range(1, n + 1):
        s = 0
        for j in range(1, k // 2 + 1):
            if k % j == 0:
                s = s + j
        if s > best_sum:
            best_k = k
            best_sum = s
    print(best_k, best_sum)
  57. Run it from this unit's folder:

    python assets/l3.py < assets/l3/1.in
  58. For k = 1 the inner loop is FOR j = 1 TO 0, which runs zero times, so the sum for 1 is 0. That is why best_k = 1 and best_sum = 0 are a correct start: they are the real first value, not a made-up one. With n = 1 (assets/l3/2.in) the outer loop runs once and the output is 1 0.

    • An ACSL FOR includes its end value, so go one past the end, in the direction of travel, for Python's range.

    • WHILE tests only at the top, so a variable can overshoot the limit before the loop stops.

    • In a running maximum, > and >= treat ties differently.

    • ACSL's int(a / b) rounds down, which is Python's //, even below zero.

    • In nested loops, run the whole inner loop for each pass of the outer one. The opening program prints 5 372.

  59. You can now trace a looping ACSL program by hand and write its exact output.