Conditionals
Contents
Conditionals#
Basic if
statements#
In the previous worksheets, we’ve seen two ways in which we can control how the lines of a program are executed (which we sometimes call the control flow of the program): we can use for
loops to repeat lines of code, and functions to separate lines of code into smaller blocks. There is only one more fundamental concept we need for manipulating the control flow. This is the ability to make decisions based on conditions.
In normal speech, when giving instructions we often use conditions. For example: “if the traffic light is red then stop”. This has a direct analogue in Python: the simple if
statement, which has the following form:
if <condition>:
body statements
next line of code
As always, the indentation is critical. The indented statements are the body of the if
statement, and will only be executed if the condition holds.
In mathematical computing, the conditions we are interested in are often relationships between numbers. For example, we can test whether a
is equal to b
using a == b
(notice there are two =
signs).
In the following example, we print out when an iteratively-defined sequence has converged.
x = 0.9
for i in range(1, 10):
x = 2*x*(1-x)
if(x == 0.5): # if the sequence has converged
print(f"after {i} iterations, x = {x}")
after 8 iterations, x = 0.5
after 9 iterations, x = 0.5
The condition x == 0.5
evaluates to one of the two boolean values True
or False
:
x == 0.5
True
x == 1
False
Note that these are not the same thing as the strings "True"
and "False"
!
True == "True"
False
Relational and logical operators#
We’ve seen that you can use ==
to test equality. These is an example of a relational operator. There are several more relational operators available in Python.
Operator |
Description |
---|---|
|
less than, less than or equal to |
|
greater than, greater than or equal to |
|
equal to |
|
not equal to |
Here’s an example of using <=
to find when the iteration above is “close to” converging.
x = 0.9
err = 0.05
for i in range(1, 10):
x = 2*x*(1-x)
if(abs(x - 0.5) <= err): # if the difference between x and 0.5 is small enough
print(f"after {i} iterations, x = {x}")
after 4 iterations, x = 0.4859262511644672
after 5 iterations, x = 0.49960385918742867
after 6 iterations, x = 0.49999968614491325
after 7 iterations, x = 0.49999999999980305
after 8 iterations, x = 0.5
after 9 iterations, x = 0.5
Divisibility and the modulo operator#
One of the conditions that we often want to test is if an integer \(a\) is divisible by another integer \(b\). In Python we do this using the modulo operator a % b
, which returns the remainder on dividing a
by b
:
a = 23
b = 5
a % b
3
You can then test if b
divides a
like so:
a % b == 0
False
As an aside, you can also get the quotient of a
by b
like so:
a // b
4
Logical Operators#
We often need to combine relational operators to form more complex conditions. For example, we may wish to test if a number is divisible by 13 and is positive. Python allows us to combine together conditions in this way:
n = 52
n % 13 == 0 and n > 0
True
There are three logical operators offered by Python:
Operator |
Description |
---|---|
|
|
|
|
|
the opposite of whatever follows it |
Here are some examples of how they work:
13 % 2 == 0 or 27 % 5 == 2
True
13 % 2 == 0 or not 27 % 5 == 0
True
The example above is already a little confusing to read; it is good practice to bracket your logical expressions when they become complex.
(13 % 2 == 0) or not (27 % 5 == 0)
True
If-else
statements#
It is very common in programming that we want the code to do one thing if a condition is true, and a different thing if it is false.
In Python, we can achieve this using if-else
statements, which have the following form:
if <condition>:
lines to run if the condition is true
else:
lines to run if the condition is false
lines to run regardless of condition
For example, suppose I want to check if I remember the value of \(\sinh(\log(2))\), and if not I want to be told the correct value.
import numpy as np
remembered_value = 5/4
if np.sinh(np.log(2)) == remembered_value:
print("Hurray!")
else:
print("Sorry, the actual value of sinh(log(2)) is", np.sinh(np.log(2)))
Sorry, the actual value of sinh(log(2)) is 0.75
Elif
statements#
We often need our programs to choose between more than two options. Suppose we have a number \(n\) and we want to compute $\(x = \begin{cases}n^2 & \text{\)2\( divides \)n\(} \\ n^3 & \text{\)3\( divides \)n\( but \)2\( does not} \\ n & \text{otherwise.} \end{cases}\)$
In order to do so, we could nest if-else
statements as in the cell below. This is just an example and would be considered bad practice, as explained afterwards.
# Don't do this! See the next text cell for a better method.
n = 3
if n % 2 == 0:
# n is even, so set x = n ** 2
x = n ** 2
else:
# if we're here n isn't even, so we test if it is divisible by 3
if n % 3 == 0:
# n is not even but is divisible by 3, so set x = n ** 3
x = n ** 3
else:
# n is neither even nor divisible by 3, so set x = n
x = n
x
27
There are other ways of nesting the if-else
statements to achieve the same result. However, nested conditionals are often extremely confusing and are a very common source of bugs in code. Sometimes it is necessary to nest conditionals, but in the example above a better alternative would be to use elif
statements. These are used inside an if
or if-else
statement, and have the following general form:
if <condition 1>:
thing to do if condition 1 holds
elif <condition 2>:
thing to do if 1 does not hold but 2 does
elif <condition 3>:
thing to do if 1 and 2 do not hold but 3 does
...
else:
thing to do if none of the conditions hold
You can have as many elif
s as you want, and you don’t have to include the else
statement. Here’s what the code above would look like using elif
:
n = 3
if n % 2 == 0:
# n is even
x = n ** 2
elif n % 3 == 0:
# n is not even since the first condition failed
# but it is divisible by 3
x = n ** 3
else:
# neither condition held, so n is neither even nor divisible by 3
x = n
x
27
Conditional return
statements#
We often combine if-else
statements with return
statements to determine what a function will return. The following function takes one argument n
, and returns \(n/2\) if \(n < 100\) and \(2n\) otherwise.
def piecewise_function(n):
if n < 100:
return n/2
else:
return 2 * n
piecewise_function(5)
2.5
piecewise_function(100)
200
When writing functions which contain return
statements, the key thing to remember is that as soon as a return
statement is reached, the function returns the given value and the function stops. This is demonstrated by the following example, which returns the smallest (larger than 1) factor of a given number.
def smallest_factor(n):
# go through the values 2 up to n in order, until we find a factor
for i in range(2, n + 1):
# if i divides n, then return i and exit this function immediately
if n % i == 0:
return i
smallest_factor(15)
3
When writing functions, we are not limited to a single return
statement inside a function: we can have as many as we want. In the following example, we use this in combination with the fact that return statements exit the function to test if a number is prime. Make you sure understand this example!
def is_prime(n):
# check whether the number has any factors (we could be cleverer about this range)
for i in range(2, n):
# if i divides n, then n is not prime (since 1 < i < n)
# so return False and exit this function
if n % i == 0:
return False
# If we get to this point, after the for loop, we haven't found any factors
# because otherwise we would have already returned False and exited the function.
# So n must be prime.
return True
is_prime(17)
True
Aside: Patterns#
The function above follows a very common code pattern. A pattern is like a “template” for solving a particular type of problem. Here the more general problem is “non-existence of an object with some property”, which we can test by iterating through all of the possibilities for that object, returning False
if one of them has the given property, and returning True
after exhausting all of the possibilities. It’s worth thinking about what patterns you are using while you code. Can you see what the analogous pattern would be for checking “existence of an object with some property”? What about “all objects have some property”, or “all objects do not have some property”?