The problem: What is output?
Here is a question of the kind you meet on every ACSL contest. It is written in ACSL pseudocode, the made-up programming language ACSL uses so that every student reads the same program, whatever language they code in.
INPUT a, b
c = a % 7
IF c > 3 && b < a THEN
d = int(a / 4) + b
ELSE IF !(b > 10) THEN
d = a - b ^ 2
ELSE
d = a + b
END IF
OUTPUT d
What is output when the input is a = 26 and b = 9?
Some of it looks like Python and some of it does not: &&, !, ^, END IF, and an int that does not quite behave like Python's.
The contest gives you no computer for this part: you read the program, trace it by hand, and write one exact answer.
This unit teaches you to do exactly that, in three steps:
how to read ACSL pseudocode and turn it into Python (Lesson 1),
how to trace IF branches without slipping (Lesson 2),
and, for Intermediate and Senior students, how to trace loops, arrays, grids and strings, which their Contest 1 also tests (Lesson 3).
Write down your first guess now.
In Lesson 1 you will translate the program into Python and check your guess; in Lesson 2 you will learn to get the answer by hand, the way the contest needs.
You will learn
- Translate ACSL pseudocode into Python with the dialect table:
/,^,!,&&,||,absandsqrt. - Use
math.floorfor ACSL'sint, which always rounds down, even below zero. - Trace branches with a trace table:
IF … ELSE IFchains, separateIFs, nested decisions, and&&before||. - Intermediate and above: trace
FORandWHILEloops, arrays, grids and strings, which your Contest 1 also tests.
Lesson 1 — Reading ACSL pseudocode
Division: Junior and above.
An ACSL program is a list of statements, one per line. The ones you meet most:
INPUT a, breads values. In a short-answer question the values are given to you, for example "the input isa = 26andb = 9".x = expressionstores a value, exactly as in Python.OUTPUT xprints.OUTPUT x, yprints both values on one line; in this book, as in Python'sprint(x, y), they are separated by one space.IF … THEN … ELSE IF … THEN … ELSE … END IFmakes decisions.END IFmarks where theIFstops, the job indentation does in Python.WHILE … END WHILEandFOR … NEXTrepeat. They are in Lesson 3.
The dialect table
Python and ACSL pseudocode agree on most things. This table lists the ones you must translate, and the traps.
| ACSL pseudocode | Python | Watch out |
|---|---|---|
=, ==, !=, <, <=, >, >= |
the same | = stores; == compares |
+, -, *, % |
the same | % is the remainder |
/ |
/ |
real division: 7 / 2 is 3.5. In Python / always gives a decimal (a float), so 6 / 3 prints 2.0 |
^ (sometimes ↑) |
** |
2 ^ 5 is 32: multiply 2 by itself five times |
! |
not |
ACSL's ! binds before everything else; in Python write not (…) with brackets |
&& |
and |
true only when both sides are true |
abs(x) |
abs(x) |
the size of x without its sign |
int(x) |
math.floor(x) |
the greatest integer <= x. Not Python's int(x), which chops toward zero: ACSL int(-1.5) is -2, Python int(-1.5) is -1 |
sqrt(x) |
math.sqrt(x) |
gives a float: math.sqrt(49) is 7.0. Questions use perfect squares or say how to round |
IF … ELSE IF … ELSE … END IF |
if … elif … else |
the first true branch runs, and only that one |
FOR k = 1 TO 9 STEP 2 … NEXT k |
for k in range(1, 10, 2) |
ACSL includes the end value (Lesson 3) |
A(3), A(r, c) |
A[3], A[r][c] |
arrays and 2D arrays (Lesson 3) |
len(S), S[j], S[a:b] |
len(S), S[j], … |
ACSL substrings are not Python slices (Lesson 3) |
One more pair: ACSL's || is Python's or, which is true when at least one side is true.
ACSL works out an expression in this order: ! first, then ^, then *, / and %, then + and -, then the comparisons, then &&, and || last.
Python's order is the same, except that not comes after the comparisons.
When you translate, add brackets to make the order you mean certain.
Real division and the remainder
Python has three division tools.
ACSL pseudocode has only / and %; a whole-number quotient is written int(a / b).
Predict each line, then check it against the output.
print(7 / 2)
print(6 / 3)
print(17 % 5)
print(17 // 5)
Output
3.5
2.0
2
3Notice
6 / 3 prints 2.0, not 2.
Python's / always gives a float, even when the division comes out even.
When a question shows a Python program, write exactly what Python prints, 2.0 included.
Powers
ACSL writes a power with ^; Python writes it with **.
A power is worked out before *, /, + and -.
print(2 ** 5)
print(10 - 3 ** 2)
print((10 - 3) ** 2)
Output
32
1
49Notice
10 - 3 ** 2 is 10 - 9, which is 1: the power goes first.
Brackets change the order, so (10 - 3) ** 2 is 49.
ACSL int is a floor
ACSL's int(x) gives the greatest integer that is less than or equal to x.
For 3.7 that is 3, and for -3.7 it is -4, because -4 is below -3.7 on the number line and -3 is above it.
Python's int(x) does something else: it chops off the decimal part, which moves a negative number up toward zero.
To get ACSL's int, use floor from Python's math module.
You met import with the random module; import math loads the math tools the same way.
import math
print(math.floor(3.7), int(3.7))
print(math.floor(-3.7), int(-3.7))
print(math.floor(-7 / 2))
Output
3 3
-4 -3
-4Notice
For positive numbers the two agree.
For negative numbers they do not: math.floor(-3.7) is -4, but int(-3.7) is -3.
So ACSL int(-3.7) is -4, and translating it with Python's int gives a wrong answer.
Always translate ACSL int(x) as math.floor(x).
Absolute value and square roots
abs(x) works the same in both languages.
ACSL's sqrt(x) is math.sqrt(x) in Python, and it always gives a float.
import math
print(abs(-8), abs(8))
print(math.sqrt(49))
print(math.sqrt(50))
print(math.floor(math.sqrt(50)))
Output
8 8
7.0
7.0710678118654755
7Notice
math.sqrt(49) is 7.0, a float.
The square root of 50 is not a whole number, so a question that uses it must say how to round; int(sqrt(50)) in ACSL, which is math.floor(math.sqrt(50)) in Python, is 7.
Not, and, or
ACSL's !, && and || are Python's not, and and or.
Put brackets after not, so it applies to the whole comparison.
b = 9
print(not (b > 10))
print(b > 5 and b < 8)
print(b > 5 or b < 8)
Output
True
False
TrueNotice
b > 10 is False, so not (b > 10) is True.
and needs both sides true; or needs just one.
Translating the opening problem
Here is the problem from the top of the unit, translated line by line.
Here the input values are written into the program, as a = 26 and b = 9, in place of reading them with input().
import math
a = 26
b = 9
c = a % 7
if c > 3 and b < a:
d = math.floor(a / 4) + b
elif not (b > 10):
d = a - b ** 2
else:
d = a + b
print(d)
Output
15Notice
Each piece of the table appears once: && became and, ! became not, ^ became **, ELSE IF became elif, and ACSL's int became math.floor.
Running the translation is how you check an answer while you practise.
In the contest you have no computer, so Lesson 2 is about getting the same answer by hand.
The full program, reading a and b from one input line, is saved as assets/l1.py.
Try it yourself
import math
parts = input().split()
a = int(parts[0])
b = int(parts[1])
c = a % 7
if c > 3 and b < a:
d = math.floor(a / 4) + b
elif not (b > 10):
d = a - b ** 2
else:
d = a + b
print(d)
Run it from this unit's folder, feeding it an input file:
python assets/l1.py < assets/l1/1.in
The files assets/l1/2.in to assets/l1/4.in send the program down each of the other branches.
Trace each one by hand first, then check with the program.
Lesson 2 — Tracing branches
Division: Junior and above.
To trace a program is to run it by hand, the way the computer would.
Keep a small table with one column per variable.
Go line by line; whenever a variable changes, write its new value in its column.
At an IF, work out the condition, write down true or false, and follow only the branch that runs.
Tracing the opening problem
Input: a = 26, b = 9.
| line | a | b | c | d | note |
|---|---|---|---|---|---|
c = a % 7 |
26 | 9 | 5 | 26 = 3 × 7 + 5 | |
IF c > 3 && b < a |
5 > 3 true, 9 < 26 true, so true |
||||
d = int(a / 4) + b |
15 | 26 / 4 = 6.5, int gives 6, and 6 + 9 = 15 |
The first condition is true, so the ELSE IF and ELSE branches are skipped entirely.
The answer is 15.
Check it against assets/l1/1.out.
Only the first true branch runs
In an IF … ELSE IF … ELSE chain the conditions are tested from the top.
The first true one runs its branch, and the rest are never looked at, even if they are true too.
Predict the output, then check it.
temp = 31
if temp > 20:
label = "warm"
elif temp > 30:
label = "hot"
else:
label = "cold"
print(label)
Output
warmNotice
31 > 30 is true, but that line is never reached, because 31 > 20 was true first.
The output is warm.
Contest questions love this trap: when you trace a chain, stop at the first true condition.
Separate IFs all get tested
Three separate IF statements are not a chain.
Each one is tested in turn, and each test sees the values left by the ones before it.
x = 7
if x > 5:
x = x - 4
if x < 5:
x = x * 3
if x % 2 == 0:
x = x + 1
print(x)
Output
9Notice
x goes 7 → 3 → 9, and 9 is odd, so the last IF does nothing: the output is 9.
Had these been one IF … ELSE IF chain, only the first branch would run and the output would be 3.
Look for END IF (or, in Python, elif) to tell the two shapes apart.
Nested conditions
An IF can sit inside a branch of another IF.
The inner test only happens if the program reaches it.
In ACSL pseudocode, each IF has its own END IF, which tells you where it stops.
IF x > y THEN
IF x % 2 == 0 THEN
z = x - y
ELSE
z = x + y
END IF
ELSE
z = y * 2
END IF
x = 9
y = 4
if x > y:
if x % 2 == 0:
z = x - y
else:
z = x + y
else:
z = y * 2
print(z)
Output
13Notice
Trace from the outside in: 9 > 4 is true, so go inside; 9 % 2 is 1, so the inner ELSE runs and z is 13.
The outer ELSE is never reached.
And before or
In both languages, && (and) is worked out before || (or), just as * comes before +.
x = 8
y = 2
print(x > 5 or y > 5 and x < y)
print((x > 5 or y > 5) and x < y)
Output
True
FalseNotice
The first line is x > 5 or (y > 5 and x < y), which is True or False, so True.
The brackets in the second line force the or first: True and False, so False.
When a condition mixes && and || without brackets, put in the brackets yourself before tracing.
Whole-number arithmetic
Many branching questions pull digits apart with % and whole-number division.
In ACSL that division is int(n / 10); in Python, n // 10 gives the same whole number.
n = 482
hundreds = n // 100
tens = n // 10 % 10
ones = n % 10
print(hundreds, tens, ones)
if hundreds + ones > tens:
print("outside wins")
else:
print("middle wins")
Output
4 8 2
middle winsNotice
n // 10 % 10 is 48 % 10, which is 8, the tens digit.
The remainder n % 10 is always the last digit, and n // 10 removes it.
Put it together: this program reads three whole numbers, the side lengths of a triangle, and prints what kind of triangle they make.
It uses a chain, a nested IF, and and/or together.
It is saved as assets/l2.py.
Try it yourself
parts = input().split()
a = int(parts[0])
b = int(parts[1])
c = int(parts[2])
if a + b <= c or a + c <= b or b + c <= a:
print("NOT A TRIANGLE")
elif a == b and b == c:
print("EQUILATERAL")
elif a == b or b == c or a == c:
print("ISOSCELES")
else:
if a * a + b * b == c * c or a * a + c * c == b * b or b * b + c * c == a * a:
print("RIGHT SCALENE")
else:
print("SCALENE")
Trace it by hand on each input before you run it from this unit's folder:
python assets/l2.py < assets/l2/1.in
Notice
The order of the chain matters.
Three equal sides are also "two equal sides", so the EQUILATERAL test must come before the ISOSCELES test; and the "not a triangle" test comes first of all.
Inputs assets/l2/1.in to assets/l2/5.in reach every one of the five outputs.
Lesson 3 — Loops, arrays, grids and strings
Division: Intermediate and above.
Junior students meet loops, arrays and strings in later contests. Intermediate and Senior students are tested on all of them in Contest 1, so this lesson covers each one in ACSL pseudocode and in Python. Tracing works the same way: a table of variables, one line at a time.
FOR loops include the end
FOR k = 1 TO 9 STEP 2
OUTPUT k
NEXT k
This outputs 1, 3, 5, 7, 9: ACSL's FOR includes the end value 9.
Python's range stops before its end, so the translation is range(1, 10, 2): one more than the end.
Without STEP, the step is 1.
for k in range(1, 10, 2):
print(k)
Output
1
3
5
7
9A negative STEP counts down.
FOR k = 20 TO 2 STEP -6 visits 20, 14, 8, 2.
Counting down, Python's range needs an end one less than ACSL's, so the translation is range(20, 1, -6).
total = 0
for k in range(20, 1, -6):
total = total + k
print(total)
Output
44Notice
The rule is "go one step past the ACSL end": add 1 when counting up, subtract 1 when counting down.
To trace a stepped loop, first write out the list of values the counter takes (20, 14, 8, 2), then trace the body once for each.
WHILE loops
WHILE condition … END WHILE is Python's while.
The test comes before each pass, so the body can run zero times.
n = 3517
reverse = 0
while n > 0:
reverse = reverse * 10 + n % 10
n = n // 10
print(reverse)
Output
7153Notice
A trace table makes this easy: n goes 3517, 351, 35, 3, 0 and reverse goes 0, 7, 71, 715, 7153.
When n reaches 0 the test fails and the loop ends.
Arrays
An ACSL array is written with round brackets: A(3) is the item at position 3.
A program says where its positions start.
Most past ACSL problems start at A(1); some start at A(0).
Python lists always start at position 0.
When an ACSL array starts at 1, the easy translation is to put an unused 0 at position 0, so that A(1) is A[1].
A(1) = 6, A(2) = 3, A(3) = 8, A(4) = 5
FOR j = 1 TO 3
IF A(j) > A(j + 1) THEN
A(j + 1) = A(j + 1) + A(j)
END IF
NEXT j
OUTPUT A(4)
A = [0, 6, 3, 8, 5]
for j in range(1, 4):
if A[j] > A[j + 1]:
A[j + 1] = A[j + 1] + A[j]
print(A[4])
Output
22Notice
Trace the array as a row of boxes and change one box at a time.
j = 1: 6 > 3, so A(2) becomes 9. j = 2: now 9 > 8, so A(3) becomes 17. j = 3: 17 > 5, so A(4) becomes 22.
Each pass sees the boxes as the pass before left them.
2D arrays: grids
A 2D array is a grid of rows and columns, like a spreadsheet or a game board.
ACSL writes one box as A(r, c): row r, column c, always in that order.
In Python a grid is a list of lists: a list of rows, each row a list.
grid[r] is a whole row, and grid[r][c] is one box in it.
grid = [[3, 8, 1],
[7, 2, 9]]
print(grid[1])
print(grid[1][2])
print(len(grid), len(grid[0]))
Output
[7, 2, 9]
9
2 3Notice
Python counts rows and columns from 0, so grid[1][2] is row 1, column 2: the 9.
len(grid) is the number of rows, and len(grid[0]), the length of the first row, is the number of columns.
To visit every box, use nested loops: rows on the outside, columns on the inside. This order, left to right along row 0, then row 1, and so on, is how you read a page.
grid = [[3, 8, 1],
[7, 2, 9]]
for r in range(len(grid)):
for c in range(len(grid[0])):
print(r, c, grid[r][c])
Output
0 0 3
0 1 8
0 2 1
1 0 7
1 1 2
1 2 9A program often builds a grid from a rule.
Build each row with append, then append the row to the grid.
Here each box holds r * c, and, as in most ACSL problems, rows and columns start at 1: row 0 and column 0 are unused zeros.
FOR r = 1 TO 3
FOR c = 1 TO 4
A(r, c) = r * c
NEXT c
NEXT r
A = []
for r in range(4):
row = []
for c in range(5):
row.append(r * c)
A.append(row)
print(A[2])
print(A[3][4], A[2][3])
Output
[0, 2, 4, 6, 8]
12 6Notice
The loops run from 0 so that the unused row 0 and column 0 exist too; after that, A[r][c] in Python is exactly A(r, c) in ACSL.
On paper, draw the grid as a table with the row numbers down the side and the column numbers across the top.
Some programs walk the grid a different way: down each column.
Swap the loops: columns outside, rows inside.
This adds up column 1 of the grid above: 1 + 2 + 3.
A = [[0, 0, 0, 0, 0],
[0, 1, 2, 3, 4],
[0, 2, 4, 6, 8],
[0, 3, 6, 9, 12]]
total = 0
for r in range(1, 4):
total = total + A[r][1]
print(total)
Output
6Strings
In ACSL, as in Python, a string's positions start at 0, len(S) is its length, S[j] is one character, and + joins strings.
Substrings are where the two differ.
ACSL's rules, for S = "ACSL WDTPD":
| ACSL | means | gives | Python |
|---|---|---|---|
S[:3] |
the first 3 characters | "ACS" |
S[:3] |
S[4:] |
the last 4 characters | "DTPD" |
S[len(S) - 4:] |
S[2:6] |
positions 2 through 6, both included | "SL WD" |
S[2:7] |
S[0] |
position 0 | "A" |
S[0] |
A Python slice S[a:b] stops before position b, and S[a:] starts at position a.
So only the first form is written the same way.
When a Python program is shown, use Python's rules; when ACSL pseudocode is shown, use ACSL's.
S = "ACSL WDTPD"
print(S[:3])
print(S[len(S) - 4:])
print(S[2:7])
print(S[4:])
Output
ACS
DTPD
SL WD
WDTPDNotice
The last line is Python's own S[4:]: everything from position 4 on, " WDTPD", starting with the space.
ACSL's S[4:] would be "DTPD".
Python programs in questions may also use the string methods you know, such as upper, lower and replace.
Real division inside a loop
A real division can change a whole number into a decimal, and ACSL's int then rounds down, even below zero.
import math
x = 5
for k in range(3):
x = x - 4
print(x / 2, math.floor(x / 2))
Output
0.5 0
-1.5 -2
-3.5 -4Notice
x goes 1, -3, -7.
-3 / 2 is -1.5, and ACSL's int(-1.5) is -2, one below, not -1.
This is exactly where Python's int would give a wrong answer.
Put it together: this program reads a grid of whole numbers and prints the column, counted from 1, with the largest total, and that total.
The first input line holds the numbers of rows and columns; each following line is one row.
If two columns tie, the one further left wins.
It is saved as assets/l3.py.
Try it yourself
parts = input().split()
rows = int(parts[0])
cols = int(parts[1])
grid = []
for r in range(rows):
row = []
for piece in input().split():
row.append(int(piece))
grid.append(row)
best_col = 0
best_total = 0
for c in range(cols):
total = 0
for r in range(rows):
total = total + grid[r][c]
if c == 0 or total > best_total:
best_col = c
best_total = total
print(best_col + 1, best_total)
Run it from this unit's folder:
python assets/l3.py < assets/l3/1.in
Notice
The outer loop is over columns, because each total runs down one column.
best_total starts from the real first column (c == 0), never from a made-up 0, so an all-negative grid (assets/l3/3.in) still works.
Recap
- ACSL's
int(x)is the greatest integer ≤ x:math.floor(-3.7)is-4, but Python'sint(-3.7)is-3. - In an
IF … ELSE IFchain only the first true branch runs; separateIFs are each tested. &&is done before||, so bracket the&&parts when you trace.- A trace table with one row per step makes the printed line easy to check. The opening program prints 15.
- Intermediate and above: a
FORloop includes its end value, and ACSL substrings are not Python slices.
You can now trace a branching ACSL program and write its exact output.