The problem: How far does the ball travel?
A ball is dropped from a height of
hcentimetres. 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, distanceWhat 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.
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 … STEPloops into Pythonrange, including loops that count down. -
Count a loop's passes before tracing it, and spot loops that never start.
-
Trace
WHILEloops, counters, accumulators and a running maximum by hand. -
Trace loops inside loops whose inner bounds depend on the outer counter.
-
Lesson 1 — Loops in ACSL pseudocode
Division: Junior and above.
ACSL pseudocode has two loops.
-
FOR k = start TO end STEP s … NEXT kcountskfromstarttoend, addingseach time. -
WHILE condition … END WHILErepeats while the condition is true.
Everything else is the dialect you learned in Unit 3:
%is the remainder,abs(x)drops the sign, andint(x)rounds down. In this unit every value is a whole number, and a division always appears insideint( … ).-
FOR includes the end value
FOR k = 4 TO 16 STEP 4 OUTPUT k NEXT kThis outputs
4,8,12and16: ACSL'sFORincludes its end value. Python'srangestops before its end, so the translation needs an end one step further on:range(4, 17, 4).for k in range(4, 17, 4): print(k)Scroll the code for more ↓
Output
4 8 12 1616is printed. Withrange(4, 16, 4)it would not be, and the loop would run one pass short. When there is noSTEP, the step is1:FOR k = 1 TO 5isrange(1, 6).Counting down with a negative STEP
A negative
STEPcounts down.FOR k = 10 TO 1 STEP -3visits10,7,4and1. Counting down, Python's end must be one below ACSL's end, so the translation isrange(10, 0, -3).total = 0 for k in range(10, 0, -3): total = total + k print(total)Scroll the code for more ↓
Output
2210 + 7 + 4 + 1is22. 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.When the end is not hit exactly
The counter does not have to land on the end value.
FOR k = 3 TO 14 STEP 4visits3,7and11. The next value,15, is past14, so the loop stops there.for k in range(3, 15, 4): print(k)Scroll the code for more ↓
Output
3 7 11The 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.
A loop that never starts
If the start is already past the end, the body runs zero times.
FOR k = 5 TO 2counts up from 5, but 5 is already past 2.count = 0 for k in range(5, 3): count = count + 1 print(count)Scroll the code for more ↓
Output
0countis still0. The same happens with a negativeSTEPwhen the start is below the end:FOR k = 1 TO 8 STEP -2never runs.How many passes?
When a
FORloop does run, the number of passes isint((end - start) / step) + 1For
FOR k = 3 TO 14 STEP 4that isint(11 / 4) + 1, which is2 + 1 = 3. Python'slengives the number of values in arange, so you can check the formula.print(len(range(3, 15, 4)), (14 - 3) // 4 + 1) print(len(range(10, 0, -3)), (1 - 10) // -3 + 1)Scroll the code for more ↓
Output
3 3 4 4Both lines agree: 3 passes, then 4 passes. Counting the passes first tells you how many rows your trace table will need.
WHILE tests at the top
WHILE condition … END WHILEThe 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.
x = 1 while x < 50: x = x * 3 print(x)Scroll the code for more ↓
Output
81xgoes1, 3, 9, 27, 81. Whenxis27the test27 < 50is true, so the body runs once more andxbecomes81. Then81 < 50is false and the loop ends. The output is81, not a number below 50.ACSL
int(a / b)is Python's//When
aandbare whole numbers, ACSL'sint(a / b)is exactly Python'sa // b. Both round down, even below zero.math.floor(a / b), from Unit 3, gives the same answer; Python's ownint(a / b)does not.import math print(17 // 5, math.floor(17 / 5)) print(-17 // 5, math.floor(-17 / 5), int(-17 / 5))Scroll the code for more ↓
Output
3 3 -4 -4 -3-17 / 5is-3.4, and rounding down gives-4.//andmath.floorboth give-4; Python'sintchops toward zero and gives-3. So translate ACSLint(a / b)asa // b. In this unit's questions,%is only used on numbers that are not negative.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.
h = 100 bounces = 0 distance = h while h >= 10: h = h * 3 // 5 bounces = bounces + 1 distance = distance + 2 * h print(bounces, distance)Scroll the code for more ↓
Output
5 372WHILE … END WHILEbecamewhilewith an indented body, andint(h * 3 / 5)becameh * 3 // 5. Each bounce adds2 * h: the ball goes uphand comes back downh. 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.The full program, reading
hfrom the input, is saved asassets/l1.py.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)Scroll the code for more ↓
Run it from this unit's folder, feeding it an input file:
python assets/l1.py < assets/l1/1.inassets/l1/2.indrops the ball from 9 cm. Predict what happens before you run it: how many times does theWHILEbody run?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
FORloop, write down the list of counter values. Before each pass of aWHILEloop, work out the test and writetrueorfalse. Fill each row from left to right, using the values in the row above.Tracing the opening problem
Input:
h = 100, sobounces = 0anddistance = 100before the loop.pass test h >= 10hbouncesdistancenote 1 100 >= 10true60 1 220 300 / 5 = 60 2 60 >= 10true36 2 292 180 / 5 = 36 3 36 >= 10true21 3 334 108 / 5 = 21.6, down to 21 4 21 >= 10true12 4 358 63 / 5 = 12.6, down to 12 5 12 >= 10true7 5 372 36 / 5 = 7.2, down to 7 – 7 >= 10falsethe loop ends The output is
5 372. Check it againstassets/l1/1.out.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, counttotal = 0 count = 0 for k in range(3, 31, 5): if k % 2 == 0: total = total + k else: count = count + 1 print(total, count)Scroll the code for more ↓
Output
54 3The counter values are
3, 8, 13, 18, 23, 28. The even ones,8 + 18 + 28, maketotal54, and the three odd ones makecount3. In a trace table, only one of the two columns changes on each row.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.
best = 0 where = 0 for k in range(1, 7): v = k * 5 % 13 if v > best: best = v where = k print(best, where)Scroll the code for more ↓
Output
12 5vgoes5, 10, 2, 7, 12, 4, sobestgoes5, 10, 10, 10, 12, 12, and the answer is12 5. Startingbestat0is safe here only becausevis never negative.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.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)Scroll the code for more ↓
Output
1 4vgoes4, 2, 0, 4, 2. The largest value,4, appears atk = 1and again atk = 4.>keeps1;>=moves on to4. Contest questions often hang on exactly this one character.Taking a number apart, digit by digit
n % 10is the last digit ofn, andint(n / 10)removes it. AWHILEloop that repeats both, untilnis0, 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, bign = 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)Scroll the code for more ↓
Output
16 8ngoes5083, 508, 50, 5, 0, anddtakes the digits3, 8, 0, 5. The output is16 8. The loop runs once per digit, which is whyWHILEfits here: the program does not know in advance how many digits there are.absinside a loopabsturns 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.pos = 0 dist = 0 for k in range(1, 7): new = k * k % 7 - 3 dist = dist + abs(new - pos) pos = new print(dist)Scroll the code for more ↓
Output
12The marker visits
-2, 1, -1, -1, 1, -2, starting from0. The jumps are2, 3, 2, 0, 2, 3, all counted as positive, so the output is12. Withoutabs, the jumps to the left would cancel the jumps to the right.Rounding down below zero, again and again
Inside a loop, a mistake with
intis repeated on every pass. Compare ACSL'sint(//) with Python'sint, which chops toward zero.x = 9 y = 9 for k in range(4): x = x // 2 - 3 y = int(y / 2) - 3 print(x, y)Scroll the code for more ↓
Output
1 1 -3 -3 -5 -4 -6 -5The two agree while the numbers are positive. At
-3, ACSL'sint(-3 / 2)isint(-1.5), which is-2, but Python'sintgives-1. From then on every pass starts from a different value, and the last line is-6 -5. The ACSL answer is-6.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 usesabs, a counter, an accumulator and a running maximum in oneWHILEloop. It is saved asassets/l2.py.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)Scroll the code for more ↓
Trace it by hand on each input before you run it from this unit's folder:
python assets/l2.py < assets/l2/1.inabscomes first, so-4096is treated as4096(assets/l2/2.in). The input0is not allowed:WHILE 0 > 0is false at once, so the program would say that0has no digits at all.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.
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 cMake one row per outer pass and write out the inner values:
ijtakesinner passes 1 1 1 2 1, 2 2 3 1, 2, 3 3 4 1, 2, 3, 4 4 So
cis1 + 2 + 3 + 4, which is10.c = 0 for i in range(1, 5): for j in range(1, i + 1): c = c + 1 print(c)Scroll the code for more ↓
Output
10The inner end
ibecomesi + 1in Python, by the same "one past the end" rule.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 hasi <= j, so every pair of numbers from 1 to 4 is visited once, with doubles such as(2, 2)included.c = 0 for i in range(1, 5): for j in range(i, 5): c = c + 1 print(i, j) print(c)Scroll the code for more ↓
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).cis4 + 3 + 2 + 1, which is10again.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 cWhen
iis1or2, the inner start3is already past the end, so the inner loop runs zero times.c = 0 for i in range(1, 6): for j in range(3, i + 1): c = c + 1 print(c)Scroll the code for more ↓
Output
6The inner passes are
0, 0, 1, 2, 3, socis6. When you make the table, writenonein the rows where the inner loop does not run, so you do not invent passes.A negative STEP outside,
intinsidet = 0 FOR i = 6 TO 1 STEP -2 FOR j = 1 TO int(i / 2) t = t + j NEXT j NEXT i OUTPUT tThe outer counter takes
6, 4, 2. The inner end isint(i / 2):3, then2, then1.t = 0 for i in range(6, 0, -2): for j in range(1, i // 2 + 1): t = t + j print(t)Scroll the code for more ↓
Output
10The three outer passes add
1 + 2 + 3, then1 + 2, then1, which makes10. Work out the inner end once per outer pass and write it at the start of the row.A WHILE loop inside a FOR loop
The inner loop can be a
WHILE. Here it takes eachkapart into its digits, as in Lesson 2. The variables the inner loop uses must be reset at the top of every outer pass.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)Scroll the code for more ↓
Output
19 10 22 4 25 7 2ktakes19, 22, 25, with digit sums10, 4, 7, socountis2. Ifs = 0were written before theFORloop instead, the digit sums would pile up:10, then14, then21.Put it together: a number's proper divisors are the numbers smaller than it that divide it exactly: for
12they are1, 2, 3, 4, 6, which add up to16. This program readsnand finds which number from1tonhas the largest sum of proper divisors, printing that number and the sum. If two numbers tie, the smaller one wins.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_sumThe inner loop stops at
int(k / 2), because no proper divisor ofkis bigger than half ofk. It is saved asassets/l3.py.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)Scroll the code for more ↓
Run it from this unit's folder:
python assets/l3.py < assets/l3/1.inFor
k = 1the inner loop isFOR j = 1 TO 0, which runs zero times, so the sum for1is0. That is whybest_k = 1andbest_sum = 0are a correct start: they are the real first value, not a made-up one. Withn = 1(assets/l3/2.in) the outer loop runs once and the output is1 0.-
An ACSL
FORincludes its end value, so go one past the end, in the direction of travel, for Python'srange. -
WHILEtests 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.
-
You can now trace a looping ACSL program by hand and write its exact output.