Prof. Andrea Gallegati
The ability to run a block of statements repeatedly.
It is legal to make more than one assignment to the same variable: it makes an existing variable refer to a new value (stop referring the old one).
x = 5
x
5
x = 7
x
7
A common source of confusion is to interpret a Python
statement like
a = b
as a mathematical proposition of equality: that a
and b
are equal.
First:
In Python
, the statement a = 7
is legal while 7 = a
is not.
Secondly: in mathematics, an equality is either true or false for all time.
In Python
, assignments can make two variables equal, but they don’t have to stay that way.
a = 5
b = a # a and b are now equal
a = 3 # a and b are no longer equal
b
5
a
3
Reassigning variables is useful, but should use it with caution.
It can make the code difficult to read and debug.
a common kind of reassignment, where the new value of the variable depends on the old.
y = y + 1
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-9-e8b256858701> in <module>() ----> 1 y = y + 1 NameError: name 'y' is not defined
if that variable did not already exist, we get an error updating it, since Python
(of course) evaluates the right side before the assignment itself.
Before the update, we have to initialize that variable with a simple assignment:
x = 0
x = x + 1
Updating by
1
is called an increment1
is called a decrement.Repeating identical or similar tasks without making errors is something that computers do well and people do poorly.
That is iteration.
We have already discussed couple examples iterating using recursion
countdown
print_n
Being so common, Python provides language features to make it easier to iterate.
Here below a countdown
version using a while statement:
def countdown(n):
while n > 0:
print(n)
n = n - 1
print('Blastoff!')
def countdown(n):
while n > 0:
print(n)
n = n - 1
print('Blastoff!')
We can almost read the while statement as if it were plain English:
“While n
is greater than 0
, display the value of n
and then decrement* n
. When you get to 0
, display the word Blastoff!”*
More formally:
true
or false
.false
, exit the while statement and continue execution at the next statement.true
, run the body and then go back to step 1
.This flow is a loop because step 3
loops back around to the top.
Here, we can prove that the loop terminates:
n
is zero or negative, the loop never runs.n
gets smaller each time, getting to 0.
In general, the body of the loop should change the value of some variables to eventually make the condition Otherwise the loop will repeat forever, that is an infinite loop. |
For some other loops, it is not so easy to tell.
def sequence(n):
while n != 1:
print(n)
if n % 2 == 0: # n is even
n = n / 2
else: # n is odd
n = n*3 + 1
The loop will continue until n
is 1
, which makes the condition false
.
Each iteration the program outputs n
and checks:
2
.n*3 + 1
.For arbitrary values of n
, it's hard to say it will ever reach 1
, i.e. the program terminates.
For example:
sequence(3)
3 10 5.0 16.0 8.0 4.0 2.0
Sometimes we can prove termination, e.g. if n
is a power of two, n
will be always even until it reaches 1.
sequence(2**5)
32 16.0 8.0 4.0 2.0
To prove this program terminates (Collatz conjecture) for all n > 0
, that's an hard question...
So far, no one has been able to prove it or disprove it!
(See http://en.wikipedia.org/wiki/Collatz_conjecture.)
If we need to end a loop just halfway through the body, we can use the break statement to jump out of the loop.
If we want to take input from the user until "done" is entered:
while True:
line = input('> ')
if line == 'done':
break
print(line)
print('Done!')
> ciao ciao > come come > va? va? > done Done!
Here the loop condition is always True, so the loop runs until it hits the break statement.
This way of writing while loops you can:
“stop when this happens” rather than “keep going until that happens”
... this is quite common: e.g. the game's loop
Loops are often used to compute numerical results by iteratively improving an approximate one.
For example, Newton’s method for computing square roots,
finds the square root of $a$, starting with almost any estimate $x$ and computing a better estimate with this formula.
a = 4 ; x = 3
y = (x + a/x) / 2
y
2.1666666666666665
The result is closer to the correct answer ($\sqrt{4}=2$).
and repeating the calculation with the new estimate, it gets even closer:
x = y
y = (x + a/x) / 2
y
2.0064102564102564
... a few more updates and the estimate is almost exact!
x = y
y = (x + a/x) / 2
y
2.0000102400262145
x = y
y = (x + a/x) / 2
y
2.0000000000262146
We don’t know how many steps it takes, but we know when we get the right answer because the estimate stops changing.
x = y
y = (x + a/x) / 2
y
2.0
When y == x
, we can stop, thus we can rewrite the above example:
a = 4 ; x = 3
while True:
print(x)
y = (x + a/x) / 2
if y == x:
break
x = y
3 2.1666666666666665 2.0064102564102564 2.0000102400262145 2.0000000000262146 2.0
It's dangerous to test float equality.
Floating-point values are only approximately right: most rational/irrational numbers can’t be represented exactly with a float.
It's safer to check (using the built-in function abs
) the difference between these two floats being less than a tolerance epsilon
.
a = 4 ; x = 3 ; epsilon = 0.0000001
while True:
print(x)
y = (x + a/x) / 2
if abs(y-x) < epsilon:
break
x = y
3 2.1666666666666665 2.0064102564102564 2.0000102400262145 2.0000000000262146
mechanical processes for solving a category of problems (e.g. Newton’s method).
Something that is not an algorithm:
When we learned to multiply single-digit numbers, we memorized the multiplication table, i.e. 100
specific solutions. This knowledge is not algorithmic.
But if we were “lazy”, we might have learned a few tricks: if these tricks are a general solution for multiplying any single-digit number, they are algorithms!
Thus, the techniques we learned for:
are all algorithms.
Algorithms do not require any intelligence to carry out.
They are mechanical processes.
According to a simple set of rules, each step follows from the last.
Executing algorithms is boring.
... but designing them is:
Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically.
Understanding natural language is a good example!
We all do it, but so far nobody explained how, from an algorithmic point of view.
(... sure? See https://platform.openai.com/examples.)
Writing bigger programs is spending more time debugging.
More code means:
To cut your debugging time try “debugging by bisection”.
If there are 100
lines of code, checking them one at a time takes 100
steps, but:
This immediately tells us if the problem was in the first/second half of the program.
Every time we halve the number of lines to search the bug for, at least in theory.
In practice it's not clear where the “middle of the program” is: don't count lines ... to find the midpoint!
Think about spots where: