Skip to main content
\( \definecolor{colordef}{RGB}{249,49,84} \definecolor{colorprop}{RGB}{18,102,241} \)
International Baccalaureate · MYP 4

Algorithms

⌚ ~106 min ▢ 11 blocks ✓ 45 exercises ➣ Prerequisites : Simultaneous Equations, Algorithms and Flowcharts, Exponents, Probability, Square and Cube Roots
I Describing and Tracing Algorithms
I.1 What an Algorithm Is
Algorithms are followed for years before anyone names them. Long division is one: whoever applies it gets the same quotient, without deciding anything. A recipe, an itinerary, the rule for the day of the week are of the same family. What they share is a finite list of steps, each one clear enough that no judgement is needed, which turns what is given at the start into what is wanted at the end. That object is the algorithm itself. It exists before any machine, and the machine is not needed to study it: an algorithm is described, executed by hand, checked, and only then written as a program.
Report an error
Definition — Algorithm
An algorithm is a finite sequence of instructions, each of them unambiguous, which transforms what is given at the start into what is wanted at the end. Unambiguous means that two people applying the instruction do the same thing. Finite means that the sequence ends, and ends for every input the algorithm accepts: after a number of steps known or not in advance, there is nothing left to do.
Definition — Inputs and output
The inputs are the values the algorithm receives at the start: it works for all of them, not for one. The output is what it produces at the end, displayed or given back. The inputs range over a domain that the statement fixes: whole numbers, positive numbers, a text. Naming the inputs, their domain and the output is the first thing to do, before any step; an algorithm may have no input at all, as one that always displays the same thing, but then that too is said.
Method — Describe an algorithm
  1. Name the inputs: what does the algorithm receive? Give each one a name.
  2. Name the output: what is wanted at the end, and in what form, displayed or given back?
  3. Write the steps in order, one instruction per line, each one applicable without deciding anything.
  4. Check that it ends: count the steps, or say what makes the last one come.
Example
Apply the four steps to this goal: display the price of a ticket, which is \(\dollar 5\) for a child under twelve and \(\dollar 8\) otherwise.

Inputs: one number, the age, a whole number of \(0\) or more. Output: the price, displayed. Steps:
  1. Read the age, call it a.
  2. If a is below 12, call the price 5; otherwise call it 8.
  3. Display the price.
Why it ends: three steps, none of them repeated. The domain of the input is part of the description and not a detail: a negative age satisfies a < 12 and would be given a price of \(\dollar 5\) — which means nothing; that is why the statement excludes it from the domain. And the boundary is said in words, below twelve, so that a child of exactly twelve pays \(\dollar 8\); leaving it out would make two readers write two different algorithms, which is the ambiguity the next example is about.

Example
Which of the two instructions below can appear in an algorithm?
  1. Add salt to taste.
  2. Add \(5\) grams of salt.

The second one. To taste asks the cook to decide, and two cooks decide differently: the instruction is ambiguous, so the recipe is not an algorithm as it stands. \(5\) grams leaves nothing to decide. A recipe is not disqualified for being a recipe, but for the instructions it leaves open; replacing each one by a measurable instruction turns it into an algorithm.

Definition — The three registers
The same algorithm is written in three ways. In natural language, as numbered sentences: the form in which it is invented and explained. In pseudo-code, a fixed and short way of writing, whose assignment is an arrow: the form in which it is studied, independent of any language. In Python, the form in which it is executed and tested. Passing from one to the other changes nothing to the algorithm, only to the way it is written. Pseudo-code is written in the language of the reader, its keywords included; the texts an algorithm displays stay in English in both registers, as does every Python line, so that a trace and a program agree letter for letter.
Example
Here is one algorithm in the three registers. It receives the two side lengths of a rectangle and displays its perimeter. Natural language.
  1. Read the length, call it length, and the width, call it width.
  2. Compute \(2\) times the sum of the two, call the result p.
  3. Display p.
Pseudo-code.
Read length
Read width
p ← 2 × (length + width)
Display p
Python.
length = int(input())
width = int(input())
p = 2 * (length + width)
print(p)

The three columns say the same thing. Natural language names the operations, pseudo-code writes them in four lines, Python adds what a machine needs and nothing more: input gives a text, int turns it into a number, and print displays. The only sign that differs in meaning is the assignment: \(\leftarrow\) in pseudo-code, = in Python, and both mean put into, never is equal to.

Method — Translate from one register to another
Each construction of the pseudo-code has a standard Python writing. Translating is replacing line by line, keeping the order and the indentation.
Pseudo-code Python
Read n n = int(input())
Display x print(x)
x \(\leftarrow\) 3 x = 3
If c then …Else …End If if c: …else: …
Else If c then elif c:
a = b  a \(\neq\) b a == b  a != b
a \(\leqslant\) b  a \(\geqslant\) b a <= b  a >= b
and  or  not and  or  not
a \(\times\) b  a \(\div\) b a * b  a / b
quotient when a is divided by b a // b
remainder when a is divided by b a \pourcent{} b
For k from 1 to n …End For for k in range(1, n + 1): …
While c …End While while c: …
Function f(a) …Return r def f(a): …return r
In the other direction, read the Python line, name what it does in a sentence, and write that sentence as a pseudo-code line. Three places need care. Read is written by Python with two functions at once, and assumes here that the value read is a whole number. The bounded loop does not write its bounds the same way. And the End If, End For and End While have no Python writing at all: what tells Python where a block stops is the indentation. The sign = is the trap of the table: in Python it assigns, where pseudo-code writes \(\leftarrow\); and to compare, pseudo-code writes = where Python writes ==. The sign \(\div\) is the other one: it is the division that keeps the decimals, and the division of whole numbers that drops them is written quotient when a is divided by b, never with the same sign.
An algorithm is not a program
The program is one writing of the algorithm, in one language, for one machine. The algorithm is what survives a change of language: the same sequence of steps, written in Python, in another language, or on paper, remains the same algorithm. That is why it can be studied without running it, and why two programs that look different can be the same algorithm.
Report an error
Ending is part of the definition
A sequence of instructions that never reaches its last step is not an algorithm, however clear each of its instructions may be. The question why does it end? therefore has an answer for every algorithm, and it will be asked at every loop. When the number of steps is known in advance, the answer is a count; when it is not, the answer says what brings the end closer at each step.
Report an error
Where the word comes from
The word comes from the name of Al-Khwarizmi, a mathematician of Baghdad of the ninth century, whose book on computing with the ten digits was translated into Latin under the title Algoritmi de numero indorum. The objects are older than the word: the algorithm that computes the highest common factor (HCF) of two numbers is in Euclid, twenty-three centuries ago, and it is still the one used today.
Report an error
Skills to practice
  • Describing and Translating an Algorithm
I.2 Variables and Choices
An algorithm needs to remember. A variable is the place where a value waits until it is used or replaced, and the arrow is what puts it there. From two or three such lines, a question already arises that no reading can settle at a glance: what is the value at the end? The answer is obtained by executing the algorithm by hand, one line at a time, writing down what changes. Executing by hand is the central technique, and its tool is the state table.
Report an error
Definition — Variable and assignment
A variable is a name that holds one value at a time. The assignment x \(\leftarrow\) e is the instruction that computes the value of the expression e with the values held at that moment, then puts the result into x, replacing what was there. Nothing else changes: the variables that are not named on the left keep their value. Python writes this instruction x = e.
Definition — The state table
The state table is the writing of an execution by hand. It has one row per instruction executed, in the order in which they are executed, and one column per variable, plus one column for what is displayed. When the body of a loop is a single instruction, one row per turn says the same thing more briefly, and that is the form used for loops. Each row carries the value of every variable after that instruction, not only of the one that changed: a row is a photograph of the whole memory. A variable that holds nothing yet leaves its cell empty.
Method — Execute an algorithm by hand
  1. Draw the table: one column per variable of the algorithm, one column Display, and a first column for the instruction.
  2. Take the instructions one at a time, in order, never two at once.
  3. Compute the right-hand side first, with the values of the previous row, then write the new row.
  4. Copy the unchanged values onto the new row, so that each row can be read alone.
  5. Read the answer in the last row, or in the Display column if the question is about what is shown.
Example
Execute the following algorithm by hand and give what it displays.
a ← 3
b ← a + 2
a ← b × 2
Display a

Instruction \(a\) \(b\) Display
\(a \leftarrow 3\) \(3\)
\(b \leftarrow a + 2\) \(3\) \(5\)
\(a \leftarrow b \times 2\) \(10\) \(5\)
Display \(a\) \(10\) \(5\) \(10\)
It displays 10. The third row is the one that must not be rushed: the right-hand side \(b \times 2\) is computed with the value then held by b, that is \(5\), and the result replaces the \(3\) that a held. Reading the algorithm as a system of equations would give \(a = 3\) and \(a = 2b\) at the same time, which is impossible; the table shows that the two lines are two moments, not two conditions.

Example
The algorithm below is meant to exchange the values of x and y. Execute it by hand and say whether it does.
x ← 4
y ← 9
x ← y
y ← x
Display x
Display y

Instruction \(x\) \(y\) Display
\(x \leftarrow 4\) \(4\)
\(y \leftarrow 9\) \(4\) \(9\)
\(x \leftarrow y\) \(9\) \(9\)
\(y \leftarrow x\) \(9\) \(9\)
Display \(x\) \(9\) \(9\) \(9\)
Display \(y\) \(9\) \(9\) \(9\)
It does not: both display \(9\). The third row destroys the \(4\), and the fourth then finds nothing but \(9\) to give back to y. The exchange needs a third variable to keep the value that is about to be crushed: \(t \leftarrow x\), then \(x \leftarrow y\), then \(y \leftarrow t\). The table is what makes the loss visible, at the exact row where it happens.

An assignment is not an equality
In mathematics, \(x = x + 1\) has no solution. As an instruction, x \(\leftarrow\) x + 1 is read from right to left and is perfectly ordinary: take the value held by x, add one, put the result back into x. The arrow says the direction, which is why pseudo-code uses it. Python writes = and keeps == for the question are these two equal?, a difference on which algorithmics and programming both insist.
Report an error
Definition — The conditional instruction
The conditional instruction chooses between two sequences of instructions according to a condition. Written If c then …Else …End If, it evaluates the condition c, which is true or false, then executes the instructions of one branch only. The Else part may be absent: nothing is then done when the condition is false. Several conditionals may follow one another, or be nested one inside the other, to treat more than two cases.
Definition — Composed conditions
A condition is built from comparisons, a = b, a < b, a \(\leqslant\) b and their like, joined by and, or, and denied by not. A condition c and d is true when both are true; c or d is true as soon as one of them is; not c is true exactly when c is false. The negation of a < b is a \(\geqslant\) b, not a > b: forgetting the case of equality is the most frequent error on conditions.
Example
Execute the algorithm below by hand for a equal to \(10\), showing how the composed condition is evaluated.
If a ≥ 0 and a ≤ 10 then
    Display "in range"
Else
    Display "out of range"
End If

Instruction \(a\) \(a \geqslant 0\) \(a \leqslant 10\) and Display
start \(10\)
If \(10\) true true true
Display \(10\) in range
It displays in range. A composed condition is evaluated one comparison at a time, and the columns show it: each half is answered on its own, then and answers true because both are. The value \(10\) is a limit of the problem, and it is accepted because both inequalities are non-strict; with a < 10 the same algorithm would refuse it. For \(11\) the first half would still be true and the second false, so and would be false and the other branch would run: one false half is enough to make an and false, where one true half would be enough to make an or true.

Method — Execute a conditional by hand
  1. Add a condition column to the state table, holding true or false for the row where the condition is evaluated.
  2. Evaluate the condition with the values of the previous row, and write the result down: it is an answer, not a computation to redo later.
  3. Follow one branch only, and write its instructions as rows; the instructions of the other branch produce no row at all.
  4. Continue after the End If, whichever branch was taken.
Example
The algorithm below receives two numbers, a and b, which are its inputs, and displays the larger. Execute it by hand for a equal to \(7\) and b equal to \(12\).
If a > b then
    m ← a
Else
    m ← b
End If
Display m

Instruction \(a\) \(b\) \(m\) \(a > b\) Display
start \(7\) \(12\)
If \(a > b\) \(7\) \(12\) false
\(m \leftarrow b\) \(7\) \(12\) \(12\)
Display \(m\) \(7\) \(12\) \(12\) \(12\)
It displays 12. The condition is false, so the Else branch runs and the Then branch produces no row: m \(\leftarrow\) a is never executed, and writing it in the table would be a mistake. When a and b are equal the condition is false too, and the algorithm displays b, which is the correct answer since the two are the same.

Example
A ticket costs \(\dollar 5\), or \(\dollar 4\) from ten tickets on, or \(\dollar 3\) from twenty on. What does the following program display?
n = 12
if n < 10:
    price = 5
elif n < 20:
    price = 4
else:
    price = 3
print("Total:", price * n)

Total: 48
The first condition is false, the second true, so the price is \(4\) and the total \(4 \times 12 = 48\). The second test does not need to say n >= 10: it is reached only when the first was false, so that part is already known. This is the chaining that pseudo-code writes as a conditional inside the Else of the previous one, and that Python writes with elif. The order of the cases is what makes it work: put the case n < 20 first and every order of fewer than ten tickets would be billed at \(\dollar 4\) instead of \(\dollar 5\).

A branch that is not taken leaves no trace
In a state table, only the instructions actually executed have a row. The temptation is to write both branches, since both are written in the algorithm; but the table is the story of one execution, not a copy of the text. Two different inputs give two different tables for the same algorithm, and comparing them is often what explains a behaviour.
Report an error
Skills to practice
  • Tracing Sequences and Choices
I.3 Repeating
Writing the same instruction twenty times gives an algorithm, but a poor one: it says twenty where the answer needed is as many as necessary, and nobody can write it when the number of repetitions is not known before starting. Repeating is therefore a construction of its own, and it comes in two kinds: the one that knows in advance how many turns it will make, and the one that turns as long as something remains true. The second raises a question the first does not: what guarantees that it stops? That question is asked here, and answered in words, well before any proof.
Report an error
Definition — The bounded loop
The bounded loop For k from a to b repeats the instructions it contains, giving k the value a, then a + 1, and so on up to b included. The variable k is the counter: it is managed by the loop itself, never assigned inside it. The number of turns is known before starting, and equals \(b - a + 1\) when \(a \leqslant b\); when \(a > b\) the loop makes no turn at all.
Definition — The accumulator
An accumulator is a variable created before the loop and read after it, which the turns build up little by little. Its starting value is the answer for zero turn: 0 for a sum, 1 for a product. When zero turn has no answer, as for the largest of several numbers (the largest of no number is not a number), the accumulator starts at the first value read, and the loop treats those that remain. Forgetting to create it before the loop, or reading it inside the loop instead of after it, are the two errors to avoid.
Method — Execute a loop by hand
  1. Write the rows before the loop as usual: the accumulator receives its starting value there.
  2. Write one row per turn, labelled by the value of the counter, and not one row per instruction of the body when the body is a single line.
  3. Compute with the values of the previous row, turn after turn, never jumping ahead.
  4. Count the turns and check the count against the bounds before reading the answer.
  5. Write the rows after the loop: the accumulator is read there, with the value of the last turn.
Example
Execute the following algorithm by hand and give what it displays.
s ← 0
For k from 1 to 4
    s ← s + k
End For
Display s

Instruction \(s\) \(k\) Display
\(s \leftarrow 0\) \(0\)
turn \(k = 1\) \(1\) \(1\)
turn \(k = 2\) \(3\) \(2\)
turn \(k = 3\) \(6\) \(3\)
turn \(k = 4\) \(10\) \(4\)
Display \(s\) \(10\) \(4\) \(10\)
It displays 10, the sum \(1 + 2 + 3 + 4\). Four turns, as announced by the bounds \(1\) and \(4\). After the turn \(k\), s holds the sum of the whole numbers from \(1\) to \(k\): the column \(s\) says so at every row, and that is why the value read after the loop is the sum of the four. The accumulator starts at 0 because that is the sum of no number at all, and it is read after the loop, when the four turns are done. Starting it at \(1\) would add one too many; reading it inside the loop would display four numbers instead of one.

Example
What does the following program display, and what does it compute? Write it also in pseudo-code.
total = 0
for k in range(1, 6):
    total = total + k * k
print(total)

55
It computes the sum of the squares of the whole numbers from \(1\) to \(5\).
total ← 0
For k from 1 to 5
    total ← total + k × k
End For
Display total
The counter takes the values \(1\), \(2\), \(3\), \(4\), \(5\), and the accumulator adds the square of each: \(1 + 4 + 9 + 16 + 25 = 55\). Reading a program the other way round is done in two steps: say in one sentence what it computes, then write that sentence as pseudo-code, line by line. The one line to watch is the loop: Python writes range(1, 6), whose second bound is the first value not taken, where the pseudo-code writes the last value taken. The same algorithm, two writings of the same bounds.

The two bounds are not written the same way
For k from 1 to n takes the value n; range(1, n) stops just before it. The Python writing of the same loop is therefore range(1, n + 1), and this shift is the most common mistake in translating from one register to the other. The safe check is to count the turns on a small case: from 1 to 3 makes three turns, and range(1, 4) gives 1, 2, 3, three values.
Report an error
Definition — The unbounded loop
The unbounded loop While c repeats the instructions it contains as long as the condition c is true. The condition is tested before each turn, including the first: when it is false at the start, the loop makes no turn. The number of turns is not known in advance; it depends on what the instructions of the body do to the values the condition looks at.
Definition — Why a loop ends
A bounded loop ends because its number of turns is fixed before it starts. An unbounded loop ends only if something in its body makes the condition false in the end. To justify that it stops, name a whole number that the body moves strictly in one direction at every turn, while the condition keeps it on one side of a bound as long as the loop turns: a whole number cannot move for ever by at least one on a bounded side, so the condition fails after a number of turns. A quantity that merely gets closer to a bound without ever reaching it proves nothing, and neither does a decrease that misses the value the condition tests. And a loop whose body does not touch what the condition looks at never stops, once it has started turning.
Method — Justify that a loop ends
  1. Name the quantity that the body changes at every turn, among those the condition looks at.
  2. Say that it is a whole number and in which direction it moves, strictly, at every turn: such a quantity is called a variant of the loop.
  3. Say which bound the condition keeps it on the right side of, as long as the loop turns.
  4. Conclude, and point at the column of the state table where that quantity is read, turn after turn.
Method — Execute an unbounded loop by hand
  1. Add a condition column to the state table: on a turn row it holds the test that licensed that turn, evaluated on the state of the row before.
  2. Write a row per turn as for a bounded loop, once the condition has been found true.
  3. Write a last row labelled end, carrying no instruction, where the condition is false: that row is what ends the trace, and forgetting it is forgetting why the loop stopped.
  4. Read the number of turns as the number of rows labelled turn.
Example
The algorithm below looks for the first power of two above \(100\). Execute it by hand and give what it displays.
p ← 1
n ← 0
While p ≤ 100
    p ← p × 2
    n ← n + 1
End While
Display n

Instruction \(p\) \(n\) \(p \leqslant 100\) Display
start \(1\) \(0\)
turn 1 \(2\) \(1\) true
turn 2 \(4\) \(2\) true
turn 3 \(8\) \(3\) true
turn 4 \(16\) \(4\) true
turn 5 \(32\) \(5\) true
turn 6 \(64\) \(6\) true
turn 7 \(128\) \(7\) true
end \(128\) \(7\) false
Display \(n\) \(128\) \(7\) \(7\)
It displays 7, since \(2^7 = 128\) is the first power of two above \(100\). The loop stops for the reason the Method asks to name: the variant is the whole number p, read in its own column; it at least doubles at every turn, so it grows by at least one each time, and the condition keeps it at most \(100\) as long as the loop turns. A whole number cannot grow for ever below \(100\), so the condition falls. The last row, end, is where it falls, and forgetting it is forgetting why the loop stopped.

Example
Two texts differ by one sign. Which of the two is an algorithm?
n ← 7
While n ≠ 0
    n ← n - 2
End While
n ← 7
While n > 0
    n ← n - 2
End While

The second one only. The first text never reaches its last step, so it is not an algorithm at all, however clear each of its instructions may be.
Instruction \(n\) \(n \neq 0\)
start \(7\)
turn 1 \(5\) true
turn 2 \(3\) true
turn 3 \(1\) true
turn 4 \(-1\) true
turn 5 \(-3\) true
… … true
  
Instruction \(n\) \(n > 0\)
start \(7\)
turn 1 \(5\) true
turn 2 \(3\) true
turn 3 \(1\) true
turn 4 \(-1\) true
end \(-1\) false
In both, n decreases by two at every turn: \(7\), \(5\), \(3\), \(1\), then \(-1\). That is where they part — on the right the condition is now false and the trace has its end line, while on the left it stays true and n goes on: \(-3\), \(-5\), and so on. What differs is the bound the condition keeps it on the right side of. On the right, n > 0 holds n at least \(1\) while the loop turns, and a whole number cannot decrease for ever above \(1\): the trace has an end row. On the left, n \(\neq\) 0 bounds n on no side at all, and the column stays true for ever: the decrease never lands on the value the condition tests, because n started odd and loses two at a time.

The condition says when to continue
While p \(\leqslant\) 100 does not mean that the loop stops at \(100\): it means that it turns as long as p is at most \(100\), and stops the first time that is false. The condition written is the condition to continue; the condition to stop is its negation. Reading it the wrong way round is what produces a trace with one turn too many or too few.
Report an error
What the loop leaves behind
After the loop, the variables keep the values of the last turn, and the one that made the condition false keeps the value that made it false. In the search for a power of two, p is worth \(128\) at the end, not \(64\): the algorithm displays n, but the value p has passed the bound. Knowing what each variable is worth after the loop is what allows an algorithm to be continued instead of restarted.
Report an error
Skills to practice
  • Executing Loops by Hand
I.4 Functions and Checking
An algorithm that is worth writing is worth naming, so that it can be used inside another without being copied. That is what a function is: a piece of algorithm with named inputs and one result. And once an algorithm is written, one question remains: how can it be known to be correct? Trying it is the first answer, and it is not enough; what remains is to say what a well-chosen trial is worth, and what it cannot say.
Report an error
Definition — Function
A function is an algorithm given a name and named inputs, called its parameters, which ends by giving back one result. Written Function f(a, b) …Return r …End Function, it does not run by itself: it runs when it is called, and each call gives its parameters the values written in the call, which are its arguments. The instruction Return ends the function at once and hands the value back to whoever called.
Definition — Displaying and giving back
Displaying shows a value to whoever is watching; giving back hands it to the algorithm that asked. A function that displays its result and gives nothing back cannot be used inside a computation: nothing remains of it once the screen is read. A function that gives back and displays nothing shows nothing, and that is normal: the caller decides whether to display it. In the state table of a call, the value handed back is written in a column of its own, Given back, since it is held by no variable.
Method — Read a function
  1. Read the header: what are the parameters called, and how many are there?
  2. Find every Return: each one is an exit, and the value it gives back is the result of that exit.
  3. Say in one sentence what the function does from its inputs, without describing its lines.
  4. Trace one call by hand, with a value for each parameter, to check that sentence.
Example
The function below is the algorithm that keeps the larger of two numbers, given a name. What does the call max_of_two(7, 12) give back?
Function max_of_two(a, b)
    If a > b then
        Return a
    Else
        Return b
    End If
End Function

Instruction \(a\) \(b\) \(a > b\) Given back
call max_of_two(7, 12) \(7\) \(12\)
If \(a > b\) \(7\) \(12\) false
Return \(b\) \(7\) \(12\) \(12\)
It gives back 12. The trace of a call begins with a row that gives the parameters their values, which is what the call does, and it ends at the Return that is reached: nothing after it is executed, in that branch or in another. The name of the function is not a variable and appears in no column; the caller is free to display the value, to put it into a variable, or to use it in a computation.

Example
What does the following program display?
def max_of_two(a, b):
    if a > b:
        return a
    else:
        return b

print(max_of_two(7, 12))
print(max_of_two(max_of_two(3, 8), 5))

12
8
The Python writing is the pseudo-code line by line, with def for the header and return for the exit. The second call shows what giving back is for: the value 8 that comes out of the inner call becomes the first parameter of the outer one, without ever being displayed or stored. A function that had displayed its result instead of giving it back could not be nested inside another call at all.

Definition — Test case
A test case is a choice of values for the inputs, together with the result expected before running anything. Checking an algorithm on a test case is executing it by hand on those values and comparing what comes out with what was expected. The expected result must be established elsewhere than by the algorithm itself, otherwise the check says nothing.
Definition — Limit case
A limit case is a test case chosen at a boundary of the problem: zero, one single value, nothing at all to treat, two equal values, a negative value, the smallest and the largest allowed. Ordinary cases show that the algorithm works when nothing is special; limit cases are where a fault hides most often, and they are chosen for that reason, not met by chance. Which values are limits depends on the domain the statement fixes.
Method — Check an algorithm
  1. Write the expected result first, for each test case, before executing anything; in the table, a column Expected carries it beside the value the algorithm produces.
  2. Take an ordinary case and execute the algorithm by hand on it.
  3. Take the limit cases: what happens for zero, for one, for equal values, for a negative value?
  4. Compare, row by row, what the table gives with what was expected, and stop at the first row where they differ.
  5. Conclude carefully: a case that fails proves the algorithm wrong; cases that pass prove nothing about the ones not tried.
Example
The algorithm below is meant to display the largest of four numbers read one after the other. It has been tried on \(3\), \(9\), \(4\), \(1\) and displayed \(9\), which is correct. Find a test case on which it is wrong.
m ← 0
For k from 1 to 4
    Read x
    If x > m then
        m ← x
    End If
End For
Display m

Four negative numbers, for instance \(-3\), \(-9\), \(-4\), \(-1\). The expected result is \(-1\); the algorithm displays \(0\).
Instruction \(m\) \(x\) \(x > m\) Display
\(m \leftarrow 0\) \(0\)
turn \(1\) \(0\) \(-3\) false
turn \(2\) \(0\) \(-9\) false
turn \(3\) \(0\) \(-4\) false
turn \(4\) \(0\) \(-1\) false
Display \(m\) \(0\) \(-1\) \(0\)
The starting value \(0\) is the fault: it is not one of the numbers read, and it wins against all of them. The safe correction is to start the accumulator at a value that is really among the numbers read, that is at the first one read; the loop then compares the three that remain. A starting value below every number the statement allows would work too, but it depends on a promise the statement must then make. The ordinary case hid the error because \(0\) happened to be smaller than every number tried.

An example does not prove
A test case that passes shows that the algorithm is correct on that case, and says nothing about the others. The maximum of the previous example passed a test and was wrong all the same. Trying remains the cheapest way to find a fault: one case that fails is enough to prove the algorithm wrong, and it points at it. Proving that an algorithm is correct for all its inputs is another matter, which calls for a sentence true at every turn of its loop (an invariant).
Report an error
Method — Find the faulty line
  1. Find a case that fails, and write down the result expected on it.
  2. Execute by hand and, at each row, ask what the value should be at that point, on a case small enough to be followed without the algorithm.
  3. Stop at the first row where the table and the expectation part company: the fault is in the instruction of that row, not before.
  4. Say what that instruction does and what it should have done, then correct it and re-run every test case, the ones that used to pass included.
Example
The function below should give back how many of its three arguments are strictly positive. The call count_positive(2, 3, 4) gives back \(1\) instead of \(3\). Which line is at fault?
Function count_positive(a, b, c)
    n ← 0
    If a > 0 then
        n ← n + 1
    End If
    If b > 0 then
        n ← n + 1
    End If
    If c > 0 then
        n ← 1
    End If
    Return n
End Function

The line n \(\leftarrow\) 1 of the third conditional, which should read n \(\leftarrow\) n + 1.
Instruction \(n\) Expected
\(n \leftarrow 0\) \(0\) \(0\)
first argument counted \(1\) \(1\)
second argument counted \(2\) \(2\)
\(n \leftarrow 1\) \(1\) \(3\)
Return \(n\) \(1\) \(3\)
The two first conditionals bring the counter to \(2\), exactly as expected; the fourth row is the first where the table and the expectation part company, and the fault is the instruction of that row. Assigning \(1\) throws away everything counted before, instead of adding one to it. The error is invisible on a call where only the third argument is positive, which is why the case chosen must have several of them.

Choosing the cases
A good set of test cases is small and varied: one ordinary case, the limits of the problem, and a case built on purpose to catch the error one suspects. Trying ten similar cases teaches less than trying three chosen ones. And when a fault has been corrected, all the cases are run again: a correction that repairs one case and breaks another is a frequent accident, which only re-running catches.
Report an error
Skills to practice
  • Checking and Correcting Algorithms
II The Classic Algorithms
II.1 Bracketing by Sweeping
Describing an algorithm, executing it by hand and checking it are already acquired. All of that applies to the algorithms that mathematics has been using for centuries: bracketing a number with no exact decimal writing, finding the highest common factor (HCF) of two numbers, deciding whether a system of two equations has a single solution, estimating a probability by throwing dice. Each of them is met in a chapter of mathematics for what it is for; here it is met for what it is: a sequence of steps that can be traced, whose ending can be justified, and whose cost can be counted. Counting that cost is what comes on top, and the sweep and the dichotomy exist to be compared.
Report an error
Definition — Bracketing
To bracket a number is to give two numbers between which it lies, and the difference of the two is the width of the bracket. A number with no exact decimal writing, such as the side of a square of area \(2\), is known by brackets of smaller and smaller width: each new digit of its decimal writing is one bracket ten times narrower. An algorithm that brackets does not give the number; it gives two numbers and the promise that the answer is between them.
Definition — The sweep
The sweep walks from a starting point towards the answer, by steps of a fixed size, and stops at the first point where a test changes its answer. It is used under one hypothesis, without which it concludes nothing: the quantity tested varies always in the same direction, so the test changes answer once on the interval walked. Under that hypothesis the answer sought is between the last point before the change and the first point after it, so the bracket obtained has the width of one step. Nothing is guessed: the algorithm only walks and tests.
Method — Bracket by sweeping
  1. Name the test that changes answer at the number sought, and a starting point where it answers one way.
  2. Choose the step: it is the width of the bracket obtained, so a step of \(0.1\) gives one decimal, a step of \(0.01\) gives two.
  3. Walk step by step as long as the test keeps its first answer.
  4. Read the bracket: the first point where the test changed, and the one before it.
  5. Sweep with whole numbers when the step is a decimal: count the steps in a whole counter and divide once at the end, never add the step to itself.
Example
The number \(\sqrt{2}\) is between \(1\) and \(2\). Execute the algorithm below by hand and give what it displays. The counter k counts tenths, so the test k \(\times\) k < 200 asks whether the square of k tenths is below \(2\).
k ← 10
While k × k < 200
    k ← k + 1
End While
Display k - 1
Display k

Instruction \(k\) \(k \times k < 200\) Display
start \(10\)
turn 1 \(11\) \(100 < 200\) true
turn 2 \(12\) \(121 < 200\) true
turn 3 \(13\) \(144 < 200\) true
turn 4 \(14\) \(169 < 200\) true
turn 5 \(15\) \(196 < 200\) true
end \(15\) \(225 < 200\) false
Display \(k - 1\) \(15\) \(14\)
Display \(k\) \(15\) \(15\)
It displays 14 then 15, which read as tenths give \(1.4 < \sqrt{2} < 1.5\). Five turns, one per tenth walked from \(1\) to \(1.5\). The loop ends for the reason every loop must name: the variant is the whole number k, which grows by one at every turn while the condition keeps its square below \(200\), and a whole number cannot grow for ever under a bound. The bracket has the width of one step, one tenth, which is exactly one decimal of \(\sqrt{2}\).

Example
What does the following program display?
k = 10
while k * k < 200:
    k = k + 1
print((k - 1) / 10)
print(k / 10)

1.4
1.5
The same algorithm, written in the other register, with the division moved to the display: the loop counts in whole tenths and the two divisions happen once each, at the end. Both registers write their decimals with a point, since a comma already separates the arguments of a call; only the prose of a French page writes them with a comma.

Definition — The cost of a sweep
The cost of an algorithm is the number of operations it makes, counted as a function of what it receives. An operation here is an arithmetic operation or a test; naming a value costs nothing. Counting is done on the algorithm, never by measuring a run. A sweep of step \(p\) over an interval of length \(L\) makes at most as many turns as there are steps in the interval, that is \(L \div p\) when the division is exact and the whole number above it otherwise, since each turn advances by \(p\) and the walk cannot leave the interval. To bracket to \(n\) decimals over an interval of length \(1\), the step is \(10^{-n}\) and the cost is at most \(10^n\) turns: it is the worst case, reached when the answer is at the far end, and one more decimal multiplies it by ten.
Method — Count the turns of a loop
  1. Say what one turn advances: one step, one unit of a counter, one halving of a width.
  2. Say what stops the loop: the bound its condition tests.
  3. Count how many advances fit between the start and that bound, as a number written with what the algorithm receives.
  4. Check the count against a trace already written, on a small case, where the turns can be counted by hand.
  5. Never measure a run: the count is a property of the algorithm, and a machine that is twice as fast changes nothing to it.
What the sweep supposes
The sweep finds the first point where the test changes, and nothing more. If the test changes twice inside one step, the walk goes over both changes without seeing them, and the bracket obtained is wrong. The algorithm is therefore used where the quantity tested varies always in the same direction, that is, where it is monotonic.
Report an error
Adding a tenth ten times
A machine writes decimals in base two, where a tenth has no exact writing, so adding 0.1 to itself accumulates a tiny error at every turn. The program below shows it.
x = 0
for k in range(10):
    x = x + 0.1
print(x)
print(x == 1)
0.9999999999999999
False
The sweep avoids the trap by counting in whole tenths and dividing once at the end. This is why the fifth step of the Method insists: a loop that adds a decimal step to itself accumulates rounding errors turn after turn, and nothing promises that they cancel out.
Report an error
Skills to practice
  • Writing, Translating and Counting Turns
II.2 Halving the Interval
The sweep walks towards the answer. There is another way of getting there: cut the interval in two, look which half holds the answer, keep it, and start again. Nothing is walked; at every turn half of what remained is thrown away. The two algorithms answer the same question and are both correct, so the only thing that separates them is what they cost, and that cost is what the comparison finally settles.
Report an error
Definition — The halving
The halving starts from a bracket [a, b] known to hold the answer, takes its middle m, and keeps the half that still holds it: [a, m] or [m, b], according to the same test the sweep used. It rests on the same hypothesis: the quantity tested varies always in the same direction, so the test says which half still holds the answer (if the answer is the middle itself, both halves hold it). Each turn divides the width by two, and the algorithm stops when the width is below the precision wanted.
What is true at every turn
The sentence the answer is between a and b is true before the first turn, because the starting bracket was chosen so, and each turn keeps it true, because the half thrown away is the one where the test says the answer is not. It is therefore true when the loop stops, and it is what makes the last bracket an answer rather than two numbers. A sentence of that kind, true at every turn of a loop, is what makes an algorithm correct for all its inputs, where a trace only shows it correct for one.
Report an error
Method — Bracket by halving
  1. Start from a bracket that holds the answer, and name the test that tells the two halves apart.
  2. Take the middle of the current bracket.
  3. Keep the half where the test still changes answer, and throw the other away.
  4. Repeat as long as the width is above the precision wanted.
  5. Read the bracket, and say the sentence that stayed true at every turn.
Example
Execute the algorithm below by hand and give what it displays. It brackets \(\sqrt{2}\) from the bracket [1, 2] down to a width of at most one tenth.
a ← 1
b ← 2
While b - a > 0.1
    m ← (a + b) ÷ 2
    If m × m < 2 then
        a ← m
    Else
        b ← m
    End If
End While
Display a
Display b

Instruction \(a\) \(b\) \(b - a\) \(b - a > 0.1\) Display
start \(1\) \(2\) \(1\)
turn 1: \(m = 1.5\), \(b \leftarrow m\) \(1\) \(1.5\) \(0.5\) true
turn 2: \(m = 1.25\), \(a \leftarrow m\) \(1.25\) \(1.5\) \(0.25\) true
turn 3: \(m = 1.375\), \(a \leftarrow m\) \(1.375\) \(1.5\) \(0.125\) true
turn 4: \(m = 1.4375\), \(b \leftarrow m\) \(1.375\) \(1.4375\) \(0.0625\) true
end \(1.375\) \(1.4375\) \(0.0625\) false
Display \(a\) \(1.375\) \(1.4375\) \(0.0625\) \(1.375\)
Display \(b\) \(1.375\) \(1.4375\) \(0.0625\) \(1.4375\)
It displays \(1.375\) and \(1.4375\): the answer is between them, and the width is \(0.0625\), below the tenth asked. Four turns, where the sweep needed five for a wider bracket. The column \(b - a\) is the one to read for the ending, and the bound it must cross is the precision \(0.1\), not zero: after \(t\) turns the width is \(1 \div 2^t\), so the loop turns as long as \(2^t\) stays below \(10\). That \(2^t\) is a whole number, it doubles at every turn, and the condition keeps it under \(10\) — a whole number cannot double for ever under a bound, so the loop ends, at the first \(t\) for which \(2^t\) reaches \(10\), that is \(t = 4\). And the sentence the answer is between a and b is true on every row, which is why the last row can be read as an answer.

Example
What does the following program display?
a = 1
b = 2
while b - a > 0.1:
    m = (a + b) / 2
    if m * m < 2:
        a = m
    else:
        b = m
print(a)
print(b)

1.375
1.4375
The same four turns. The halves of a half are written exactly in base two, so this loop does not drift as the repeated addition of a tenth did: every middle it computes is an exact decimal. That is one more reason to prefer it, beyond its cost.

Definition — The cost of a halving
Each turn divides the width by two, so after \(t\) turns a starting width \(L\) has become \(L \div 2^t\). Reaching a width at most \(p\) therefore takes the first number of turns \(t\) for which \(2^t\) reaches \(L \div p\): the cost grows like the number of times \(2\) must be multiplied by itself to reach that quotient, and not like the quotient itself. In decimals, gaining one more decimal costs between three and four turns, since \(10\) lies between \(2^3 = 8\) and \(2^4 = 16\).
The first contrast of costs
The two algorithms bracket the same number and are both correct. For one decimal over a whole unit interval the sweep makes ten turns at worst and the halving four, as its own trace shows; the example of the sweep made five, because the answer was met halfway. For six decimals the sweep makes a million turns and the halving about twenty. The gap does not come from the writing, which is shorter for the sweep, but from what each turn throws away: the sweep eliminates one step, the halving eliminates half of what remains. Comparing two correct algorithms means comparing that, and nothing else.
Report an error
What the halving supposes
The halving needs a starting bracket that really holds the answer, and a test that answers one way at one end and the other way at the other. Without that first bracket it has nothing to cut; with a quantity that changes answer twice inside the interval it keeps a half where the answer may not be, and its promise falls with it. Those two hypotheses are established first; the algorithm only relies on them.
Report an error
Skills to practice
  • Executing and Justifying a Halving
II.3 Factors, Primes and the HCF
The two previous sections looked for a number with no exact decimal writing. This one asks questions about whole numbers that have exact answers: how many factors a number has, what is the highest factor two numbers share. The algorithms are ancient and short, and each of them carries the same three questions as the others: does it end, is it correct, what does it cost. The last one has a surprise here: one of the algorithms costs far less than its obvious writing, for a reason that is a piece of arithmetic and not a piece of programming.
Report an error
Definition — Counting the factors
A whole number k divides n, or is a factor of n, if the remainder when n is divided by k is zero. For a whole number n of at least \(1\), the obvious algorithm tests every k from 1 to n and counts those that divide: it is correct, it ends after n turns, and it costs n tests. For a number of ten digits that is of the order of a thousand million tests, and for one of twenty digits ten thousand million times more, which no machine will run at all.
Method — Stop at the square root
  1. Pair the factors: if k divides n, then the quotient when n is divided by k divides n too, and the two multiply to n.
  2. Note that one of the two is small: in a pair whose product is n, both cannot be above the square root of n, or their product would pass n.
  3. Test only the small ones, that is the k such that k \(\times\) k \(\leqslant\) n, and count each pair found as two factors.
  4. Count once the k whose pair is itself, that is when k \(\times\) k = n.
The test k \(\times\) k \(\leqslant\) n is what replaces a square root here: it asks the same question with a multiplication, which is exact on whole numbers where a square root would not be.
Example
Execute the algorithm below by hand for n equal to \(36\) and give the value it displays.
c ← 0
k ← 1
While k × k ≤ 36
    If remainder when 36 is divided by k = 0 then
        If k × k = 36 then
            c ← c + 1
        Else
            c ← c + 2
        End If
    End If
    k ← k + 1
End While
Display c

Instruction \(k\) \(c\) \(k \times k \leqslant 36\) found Display
start \(1\) \(0\)
turn \(k = 1\) \(2\) \(2\) true pair \((1, 36)\)
turn \(k = 2\) \(3\) \(4\) true pair \((2, 18)\)
turn \(k = 3\) \(4\) \(6\) true pair \((3, 12)\)
turn \(k = 4\) \(5\) \(8\) true pair \((4, 9)\)
turn \(k = 5\) \(6\) \(8\) true nothing
turn \(k = 6\) \(7\) \(9\) true \(6 \times 6 = 36\)
end \(7\) \(9\) false
Display \(c\) \(7\) \(9\) \(9\)
The factors of \(36\) are \(1\), \(2\), \(3\), \(4\), \(6\), \(9\), \(12\), \(18\) and \(36\). It displays 9. Six turns instead of thirty-six, and every factor is counted: the four pairs give eight, and \(6\), whose companion is itself, gives the ninth. The condition column shows where the loop stops, at \(k = 7\), since \(49\) passes \(36\). The loop ends for the usual reason: the variant is the whole number k, which grows by one at every turn while the condition keeps its square at most \(36\), and a whole number cannot grow for ever under a bound. The algorithm never looks above the square root, and it does not need to.

Definition — Deciding whether a number is prime
A whole number is called prime when it is at least \(2\) and has no factor other than \(1\) and itself. The algorithm tests the k from 2 while k \(\times\) k \(\leqslant\) n, and answers no at the first one that divides; if none does, it answers yes. The answer is a truth value, written true and false in the pseudo-code and True and False in Python.
Method — Decide whether a number is prime
  1. Reject the numbers below 2 first: they are not prime, and the loop would say nothing about them since it makes no turn.
  2. Walk the possible factors from 2, as long as k \(\times\) k \(\leqslant\) n.
  3. Leave at the first factor found: one is enough to answer no, and finishing the loop would cost turns for nothing.
  4. Answer yes after the loop, which is reached only when no factor was found.
Function is_prime(n)
    If n < 2 then
        Return false
    End If
    k ← 2
    While k × k ≤ n
        If remainder when n is divided by k = 0 then
            Return false
        End If
        k ← k + 1
    End While
    Return true
End Function
Example
What does the call is_prime(91) give back? Execute it by hand.

Instruction \(k\) (after the turn) \(k \times k \leqslant 91\) divides Given back
call is_prime(91)
turn \(k = 2\) \(3\) true no
turn \(k = 3\) \(4\) true no
turn \(k = 4\) \(5\) true no
turn \(k = 5\) \(6\) true no
turn \(k = 6\) \(7\) true no
Return false \(7\) true yes, \(91 = 7 \times 13\) false
It gives back false. The sixth turn tests \(7\), which divides \(91\), and the Return ends the call there: the loop is never finished, and the turns that would have gone up to \(9\) are never made. That early exit is why a number that is not prime often costs less than a prime number of the same size: the test stops at its smallest prime factor instead of going up to the square root — unless that factor is itself close to the square root, as for \(49 = 7 \times 7\).

Example
What does the following program display?
def is_prime(n):
    if n < 2:
        return False
    k = 2
    while k * k <= n:
        if n % k == 0:
            return False
        k = k + 1
    return True

print(is_prime(91))
print(is_prime(97))

False
True
For \(91\) the loop finds \(7\), which divides it, and leaves at once: \(91 = 7 \times 13\). For \(97\) it tries \(2\), \(3\), \(4\), \(5\), \(6\), \(7\), \(8\) and \(9\), and stops because \(10 \times 10\) passes \(97\); none divides, so the answer is True. The loop ends for the same reason as the previous one: k is a whole number growing by one at every turn, held by the condition under the bound n. And the first return is what makes the algorithm cheap on a number that is not prime: it does not finish the loop, it leaves at the first proof. And eight tests are enough to certify \(97\), where testing up to \(96\) would have made ninety-five.

Definition — Euclid's algorithm
The highest common factor (HCF) of two whole numbers is the largest number that divides both. Euclid's algorithm computes it by remainders: as long as the second number is not zero, replace the pair (a, b) by the pair (b, r), where r is the remainder when a is divided by b; when the second becomes zero, the first is the answer. The two numbers are whole and at least one is not zero, since a pair of two zeros has no HCF: every whole number divides zero.
Method — Justify Euclid's algorithm
  1. Ending: the second number of the pair is a whole number, and at every turn it becomes the remainder when a is divided by b, so it is strictly smaller than the b it replaces, and a remainder is never negative. A whole number cannot decrease for ever above zero, so the second number reaches zero.
  2. Correctness: write the division as \(a = q \times b + r\). A number that divides a and b divides \(a - q \times b\), that is r; and a number that divides b and r divides \(q \times b + r\), that is a. The two pairs therefore have exactly the same common factors, and the highest of them travels unchanged from turn to turn.
  3. Reading the answer: when the second is zero, the common factors of the pair are the factors of the first, whose highest is the first itself.
  4. Point at the column of the state table that carries the argument: the second number, strictly decreasing, is the variant; the HCF, unchanged, is what stays true at every turn.
Example
Execute Euclid's algorithm by hand on \(1071\) and \(462\), and give the value it displays.
a ← 1071
b ← 462
While b ≠ 0
    r ← remainder when a is divided by b
    a ← b
    b ← r
End While
Display a

Instruction \(a\) \(b\) \(r\) \(b \neq 0\) Display
start \(1071\) \(462\)
turn 1 \(462\) \(147\) \(147\) true
turn 2 \(147\) \(21\) \(21\) true
turn 3 \(21\) \(0\) \(0\) true
end \(21\) \(0\) \(0\) false
Display \(a\) \(21\) \(0\) \(0\) \(21\)
It displays 21. Three turns, and the column \(b\) carries both arguments at once: it holds \(462\), \(147\), \(21\), \(0\), strictly decreasing, which is why the loop ends; and the HCF of the pair on each row is \(21\) on every one of them, which is why the last row can be read as the answer. Written as divisions, the three turns are \(1071 = 2 \times 462 + 147\), then \(462 = 3 \times 147 + 21\), then \(147 = 7 \times 21 + 0\): the algorithm is the chain of those divisions, and nothing else.

Three costs met so far
The sweep costs as many turns as there are steps to walk, at most \(10^n\) for \(n\) decimals. The halving costs the number of times two must be multiplied by itself to reach the same precision, between three and four turns per decimal. The factor test costs as many turns as there are whole numbers up to the square root of n. Three growths, three algorithms, and the same way of measuring: count the turns as a function of what the algorithm receives, on the algorithm itself, without running it.
Report an error
Skills to practice
  • Tracing and Justifying Factors, Primality and Euclid
II.4 Deciding and Simulating
Not every algorithm repeats. Some answer a question by computing one number and looking at it: whether a system of two equations has a single solution. Their cost is the same whatever they receive, and their whole difficulty is in the number computed, which comes from mathematics and not from the algorithm. Others repeat a great many times on purpose, because what is wanted is not one answer but a proportion: throwing a die a thousand times to see how often a six comes out. The two kinds are set side by side, and the comparison closes on what a simulated frequency does and does not say.
Report an error
Definition — Deciding by a computation
Some questions are answered without any repetition: one number is computed from what is given, and the answer is read on that number, most often on whether it is zero. Such an algorithm has no loop, so it has nothing to justify about its ending, and its cost is a fixed number of operations, counted the same way as for every other algorithm. Two inputs may take two branches of different lengths, but neither length grows with the numbers received. All it needs is a mathematical result saying which number to compute and how to read it.
Method — Solve a system of two equations by its determinant
The system is \(a_1x + b_1y = c_1\) and \(a_2x + b_2y = c_2\). Multiplying the first equation by \(b_2\), the second by \(b_1\), and subtracting the second from the first eliminates \(y\): \((a_1b_2 - b_1a_2)x = c_1b_2 - b_1c_2\). The number \(d = a_1b_2 - b_1a_2\) is called the determinant of the system. When \(d\) is not zero, dividing by it gives \(x = (c_1b_2 - b_1c_2) \div d\), and eliminating \(x\) in the same way gives \(y = (a_1c_2 - c_1a_2) \div d\).
  1. Compute the determinant \(d = a_1b_2 - b_1a_2\) from the four coefficients of the unknowns.
  2. Read the case on it: when \(d\) is zero the system has no single solution, and the algorithm says so instead of computing.
  3. Otherwise apply the two quotients that solve the system, and display them.
  4. Count what it costs: no loop, so the same operations for every system, and the branch is the only thing that varies.
d ← a1 × b2 - b1 × a2
If d = 0 then
    Display "no single solution"
Else
    x ← (c1 × b2 - b1 × c2) ÷ d
    y ← (a1 × c2 - c1 × a2) ÷ d
    Display x
    Display y
End If
Example
Execute the algorithm of the Method by hand on the system \(2x + 3y = 8\) and \(5x - y = 3\), whose coefficients are \(a_1 = 2\), \(b_1 = 3\), \(c_1 = 8\), \(a_2 = 5\), \(b_2 = -1\), \(c_2 = 3\), and give what it displays.

Instruction \(d\) \(x\) \(y\) \(d = 0\) Display
start
\(d \leftarrow a_1b_2 - b_1a_2\) \(-17\)
If \(d = 0\) \(-17\) false
\(x \leftarrow (c_1b_2 - b_1c_2) \div d\) \(-17\) \(1\)
\(y \leftarrow (a_1c_2 - c_1a_2) \div d\) \(-17\) \(1\) \(2\)
Display \(x\) \(-17\) \(1\) \(2\) \(1\)
Display \(y\) \(-17\) \(1\) \(2\) \(2\)
It displays \(1\) then \(2\). The determinant is \(2 \times (-1) - 3 \times 5 = -17\), not zero, so the second branch runs; then \(x = (8 \times (-1) - 3 \times 3) \div (-17) = 1\) and \(y = (2 \times 3 - 8 \times 5) \div (-17) = 2\). Checking is one line: \(2 \times 1 + 3 \times 2 = 8\) and \(5 \times 1 - 2 = 3\). Twelve operations — six multiplications, three subtractions, two divisions and one test — no loop, and the branch that answers no single solution produces no row at all here — it is where the mathematical hypothesis is tested, and without it the algorithm would divide by zero.

Example
A system \(a_1x + b_1y = c_1\) and \(a_2x + b_2y = c_2\) whose determinant \(a_1b_2 - b_1a_2\) is not zero has one solution, given by \(x = (c_1b_2 - b_1c_2) \div d\) and \(y = (a_1c_2 - c_1a_2) \div d\), where \(d\) is that determinant. What does the following program display? It solves the system \(2x + 3y = 8\) and \(5x - y = 3\).
a1 = 2
b1 = 3
c1 = 8
a2 = 5
b2 = -1
c2 = 3
det = a1 * b2 - b1 * a2
if det == 0:
    print("no single solution")
else:
    print((c1 * b2 - b1 * c2) / det)
    print((a1 * c2 - c1 * a2) / det)

1.0
2.0
The determinant is \(2 \times (-1) - 3 \times 5 = -17\), which is not zero, so the system has one solution and the two formulas give it: \(x = (8 \times (-1) - 3 \times 3) \div (-17) = 1\) and \(y = (2 \times 3 - 8 \times 5) \div (-17) = 2\). Checking is one line of arithmetic: \(2 \times 1 + 3 \times 2 = 8\) and \(5 \times 1 - 2 = 3\). The algorithm displays decimals because the division always gives one, even when the quotient is whole. And the branch that answers no single solution is not a detail: it is where the mathematical hypothesis is tested, and without it the program would divide by zero.

Definition — Simulating
To simulate an experiment is to have a machine draw at random what chance would have given, a great many times, and to count. The frequency of an outcome is the number of times it came out divided by the number of draws. Simulating gives an estimate and not a value: two runs of the same program usually give two different frequencies. It is what one does when no exact computation is at hand, and it is also a way of seeing a probability that is already known exactly.
Method — Estimate a frequency by simulation
  1. Name the outcome counted, and the number of draws.
  2. Create the counter at 0 before the loop, as for any accumulator.
  3. Repeat the draw the number of times chosen, and count the draws that give the outcome.
  4. Divide the counter by the number of draws, after the loop.
  5. Say what the result is worth: it is a frequency, not a probability. With many draws it is usually close to the probability; it may happen to fall exactly on it, and that is chance too, not a computation.
Example
Execute the algorithm below by hand. The numbers read are, in order, \(6\), \(2\), \(6\), \(4\), \(1\) and \(5\).
c ← 0
For i from 1 to 6
    Read d
    If d = 6 then
        c ← c + 1
    End If
End For
Display c

Instruction \(d\) \(c\) \(d = 6\) Display
start \(0\)
turn 1 \(6\) \(1\) true
turn 2 \(2\) \(1\) false
turn 3 \(6\) \(2\) true
turn 4 \(4\) \(2\) false
turn 5 \(1\) \(2\) false
turn 6 \(5\) \(2\) false
Display \(c\) \(5\) \(2\) \(2\)
It displays 2, so the frequency of the six over these six draws is \(\frac{2}{6}\), that is \(\frac{1}{3}\) — twice the probability, which is what six draws are worth. The algorithm is an accumulator inside a bounded loop, and its cost is read off the bounds without running anything: exactly one turn per draw, so n turns for n draws. That is the one cost that the person writing the algorithm chooses.

Example
The program below throws a die a thousand times and displays the frequency of the six. What can be said of the number it displays?
import random

c = 0
for i in range(1000):
    d = random.randint(1, 6)
    if d == 6:
        c = c + 1
print(c / 1000)

A number usually close to \(0.167\), and usually a different one at each run: \(0.158\), then \(0.174\), then \(0.165\). Nothing certain can be said in advance, and that is the point. The probability of the six is \(\frac{1}{6}\), a number the mathematics of chance gives exactly; the frequency displayed is what a thousand draws happened to give, and there is no reason for it to be that number — a fraction \(\frac{c}{1000}\) can never equal \(\frac{1}{6}\) exactly, but it can land very close (\(0.167\) for \(c = 167\)). Multiplying the draws by a hundred makes large gaps far less likely without making them impossible: a simulation estimates, it does not compute.

A frequency is not a probability
The two are told apart by what they depend on: a probability is fixed by the experiment, a frequency by the draws that happened. Running the program twice usually shows it, since the two runs usually differ — and if they happen to agree, that changes nothing: a frequency may change from one run to the next, and a probability may not. This is why an answer that reads the probability is \(0.158\) is wrong even when the number is close: what the simulation gives is an estimate, and saying so is part of the answer. Probability theory says what the exact value is and why the frequencies gather around it; the algorithm only produces them.
Report an error
The costs of the classic algorithms
Six costs can be compared. An algorithm without a loop costs the same for every input. The halving costs a number of turns that grows like the times two must be multiplied by itself. Euclid's algorithm is observed to take very few turns even on large numbers — three on \(1071\) and \(462\) — and why that is always so is a result that counting turns does not establish. The factor test costs the whole numbers up to the square root. The simulation costs exactly the number of draws asked for. The sweep costs ten times more for each decimal gained. Counting the turns, on the algorithm and without running it, comes before naming those growths.
Report an error
Skills to practice
  • Predicting What an Algorithm Decides
  • Deciding by a Computation and Simulating
III Going further
Skills to practice
  • Solving Problems in Context
  • Investigating Patterns in Algorithms
  • Quiz