= -3
x if x < 0:
print(f'{x} is a Negative number')
-3 is a Negative number
There are two primary tools for control flow: choices and loops. Choices, like if
statements allow you to run different code depending on the input. Loops, like for
and while
, allow you to repeatedly run code, typically with changing options
The basic form of a condition is the if statement.
In If
control flow, if the condition
is True
then true_action
is evaluated. If the condition is false, nothing happens.
if condition:
true_action
= -3
x if x < 0:
print(f'{x} is a Negative number')
-3 is a Negative number
= 3
x if x < 0:
print(f'{x} is a Negative number')
if condition:
true_action
else:
false_action
In if else statement, if the condition
is True
then true_action
is evaluated. otherwise the false_action
is evaluated.
= 3
x if x < 0:
print(f'{x} is a Negative number')
else:
print(f'{x} is a positive number')
3 is a positive number
Sometimes you need to evaluate a lot of statements before you find the value you want.
= 3
x if x > 0:
= 3
z = z * x
y else:
= -0.03
z = z * 10
p = x * p
y y
9
In other langauges this is commonly known as else if
statements
if condition_one:
action_one
elif condition_two:
action_two
else:
false_action
In this case, we have various conditions. Only one of the conditions where the condition evaluates to True
will be evaluated. If none of the conditions is True
then the else statement is evaluated.
= "plant"
x if x == "cow" or x == "horse" or x == "dog": #same as: x in ('cow','horse', 'dog')
print(4, 'limbs')
elif x in ("human", "bird"):
print(2, 'feet')
elif x == "plant":
print(0, 'limbs')
else:
print("Unknown input")
0 limbs
These are just if-else statements inside other if-else statements to provide better decision making
Note the indentation. The statements to be evaluated are indented to ensure that they are in the right choice operation.
Sometimes the statements to be evaluated are very short. In that case, we can use a tertiary statement. This looks like shown below
true_action if condition else false action
= 3
x print('Negative') if x < 0 else print('positive')
positive
# get absolute of a number
= -3
y = -y if y < 0 else y
y y
3
This is useful in cases where we need to assign a value to a variable based on a simple condition, and we want to keep our code more concise — all in just one line of code. It’s particularly handy when you want to avoid writing multiple lines for a simple if-else situation.
Suppose we had a function defined like below:
\[ y = \begin{cases} 1 - a& \text{if } a < -3\\ a &\text{if } -3\le a\le3\\ 2a & \text{ Otherwise }\end{cases} \]
We could write it using if else if statement:
def f(a):
if a < -3:
= 1 - a
y elif -3 <= a <= 3:
= a
y else:
= 2 * a
y return y
-6) f(
7
-3) f(
-3
0) f(
0
10) f(
20
Take note that the function name is different from the variable within the function. We have to ensure this is the case in order to disambiguate the function from the variable.
At the same time, we could use the ternary operator:
def y(a):
return 1 - a if a < -3 else a if -3 <= a <= 3 else 2*a
-6) y(
7
-3) y(
-3
0) y(
0
10) y(
20
The above is a nested ternary operator. Ternary does not use elif
expressions but rather uses a nested if expression. In the above, the nested if depicts the elif statement shown in the f
function. Note that we could also entirely get rid of the if statements by just doing basic computations. This is usually not the case. \((1-a)\mathbb{I}_{(a < -3)} +a\mathbb{I}_{(-3\le a\le 3)} + 2a\mathbb{I}_{(a>3)}\) where \(\mathbb{I}_{(.)} = \begin{cases}1& \text{if condition }(\cdot) \text{ is met}\\ 0 & \text{otherwise}\end{cases}\). In our case, \(\mathbb{I}\) is just the logical value returned by the condition. Refer to question3 exercise 1.
Note that when R parses the statements above, else
is taken to be a stand alone and not part of the if
Another example:
= -3
x = x if x > 0 else 0 # What is the value of y?
y y
0
This iterates over items in an iterable container. An iterable is any Python object capable of returning its members one at a time, permitting it to be iterated over. Most common iterables include str
list
tuple
dict items
and range
for loop has the following basic form.
for item in iterable:
perform_action
perform_action
is called once for each item in iterable
for i in range(0,3):
print(i)
0
1
2
A range is simple a container that holds three basic numbers. The start
, stop
and step
of a sequence. eg range(1,10,2)
. You could convert this to a list and therefor enumerate all the values associated to the range:
= range(1,10,2)
x x
range(1, 10, 2)
list(x) # enumerate all values associated with range object x
[1, 3, 5, 7, 9]
list(range(10)) # just input one single number
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(range(20, 15, -1)) # negative step
[20, 19, 18, 17, 16]
In all the values of a range, the stop
value is excluded from the range. Therefore, range(n)
produces the values from 0
to n - 1
. This comes in handy when generating indices to index an iterable. As the indexing in python starts from 0, the maximum index for any iterable, will be the length of the iterable minus 1. Thus we could use range(len(iterable))
and this will provide us will all the indices without getting an error.
Note that we could use membership operator in
to determine as to whether an object belongs to a range.
2 in x
False
7 in x
True
2 is not in the range while 7 is in the range. The membership test is done using a formula.
\[ \left(\text{number to test} - \text{start}\right) mod \text{ step} = 0 \]
ie we test for equality to 0:
= 1
start = 2
step 2 - start) % step == 0 (
False
7 - start) % step == 0 (
True
Since the elements of a range are produced on demand, there should be no need of listing all its elements. Assume the range covers tens of billions of elements, your computer does not have memory to list all the elements. eg creating a range object representing a sequence of 1billion numbers takes 48bytes while listing all the objects requires 80GB. Better use the range object.
Think of a range as a formula that generates a sequence while keeping track of the currently generated number. We can thus loop over these numbers and take an action.
Altering values in a list
= [10, 20, 30, -10]
x for index in range(len(x)): # iterating over the indices of x
= x[index]**2
x[index] x
[100, 400, 900, 100]
creating a new list, while maintaining the old list:
= [10, 20, 30, -10]
x = []
y for element in x: # iterating over the actual elements of x
**2)
y.append(element x,y
([10, 20, 30, -10], [100, 400, 900, 100])
Apart from range
, there is also an enumerate
d object. This is an object that contains the values of the list together with the associated indices.
= [10, 20, 30, -10]
x list(enumerate(x))
[(0, 10), (1, 20), (2, 30), (3, -10)]
We could thus loop though the enumerated object and carry out a predetermined action.
for idx,val in enumerate(x):
print(f'Number at position {idx} is {val}. its square is {val**2}')
Number at position 0 is 10. its square is 100
Number at position 1 is 20. its square is 400
Number at position 2 is 30. its square is 900
Number at position 3 is -10. its square is 100
For now we did a simple printing. But sometimes the computation is complex.
The last object to talk about is the zip
object. zip is used to bundle two or more elements of iterable objects together as a tuple.
= [1, 2, 5, 10]
x = [3, 9, 11, 15]
y list(zip(x,y))
[(1, 3), (2, 9), (5, 11), (10, 15)]
You could zip as many objects as you want as long as they are all of the same length. If not of the same length, then the zip will ignore the extra elements of the longer object
= [1, 2, 5, 10]
x = [3, 9, 11]
y list(zip(x,y))
[(1, 3), (2, 9), (5, 11)]
In the example above, the element 10
from list x
was ignored.
How can we use this in for-loops?
= [1, 2, 5, 10]
x = [3, 9, 11, 15]
y = []
z for elem_x, elem_y in zip(x,y):
+ elem_y)
z.append(elem_x z
[4, 11, 16, 25]
= ['Joe','Earnst','Thomas','Martin','Charles']
first_name = ['Schmoe','Ehlmann','Fischer','Walter','Rogan','Green']
last_name = [23, 65, 11, 36, 83]
age
for first_name, last_name, age in zip(first_name, last_name, age):
print(f"{first_name} {last_name}\t is {age} years old")
Joe Schmoe is 23 years old
Earnst Ehlmann is 65 years old
Thomas Fischer is 11 years old
Martin Walter is 36 years old
Charles Rogan is 83 years old
Example: Computing the factorial of a number. \(n! = 1\times2\times3\cdots\times n\) with the added notion that \(0! = 1\)
def factorial(n):
= n
result for i in range(1, n):
*= i
result return result
6) factorial(
720
question: Explain as to why we set the result to n.
Using for loops we could easily write functions to compute sum
, max
, min
, etc. Below are simple examples of sum
and max
def my_sum(x):
= 0
results for val in x:
+= val
results return results
1,2,3,4,8,9,10]) my_sum([
37
def my_max(x):
= x[0]
results for val in x:
if val > results:
= val
results return results
1,2,3,20,4,8,9,10]) my_max([
20
Python provides a quirky an exiting pythonic way to manipulate lists.
These are one liner for loops almost similar in structure to the ternary operator. They are often faster than normal for-loops because they use a more optimized internal mechanism for iterating over the collection. Additionally, they allow you to perform transformations and filtering in a single statement, which can lead to more efficient code.
The syntax looks like shown below:
[perform_action for item in iterable]
= [1, 2, 5, 10]
x = [3, 9, 11, 15]
y = [elem_x + elem_y for elem_x, elem_y in zip(x,y)]
z z
[4, 11, 16, 25]
This resembles the normal for loop introduced earlier. The difference being where the action to be performed is placed.
While all list comprehensions can be written as a for-loop, not all for-loops can be written as a list comprehension. eg Factorial loop above cannot be written as a list comprehension.
Note that list comprehensions can also include branches ie the if and if else conditions. No elif expressions can be used in list comprehension
Suppose we want to filter even values from a list on numbers:
= [1,6,24,3,7,8,12,13]
x for i in x if i % 2 == 0] [i
[6, 24, 8, 12]
Could you explain the code above?
We can also decide to manipulate some numbers in one way and other numbers in another way. For example, if the number is less than 10, square the number otherwise get half the number:
\[ y = \begin{cases} x^2 & x < 10\\ \frac{x}{2}& x\ge10\end{cases} \]
**2 if i < 10 else i/2 for i in x] [i
[1, 36, 12.0, 9, 49, 64, 6.0, 6.5]
Take note that the first part is ternary. We invoke the use of nested ternary expressions. If the code becomes complicated, revert back to using the normal for-loop.
What if we had multiple conditions? Use nested ternary operations.
This is similar to the list comprehension, although we create a new dictionary instead of a list:
= [1,6,24,3,7,8,12,13]
x for key, val in enumerate(x)} {key:val
{0: 1, 1: 6, 2: 24, 3: 3, 4: 7, 5: 8, 6: 12, 7: 13}
Note that there must be a mapping between a key and a value prior to the for loop
**2 for i in x} {i:i
{1: 1, 6: 36, 24: 576, 3: 9, 7: 49, 8: 64, 12: 144, 13: 169}
Unlike the for-loop, this takes a condition and as long as the condition is satisfied, the iteration is carried out.
while condition:
perform_action
= 0
count while count < 5:
print(count)
## Compute other stuff in here
+= 1 # increase x by 1 count
0
1
2
3
4
Notice that The increment statement is at the very end. Depending on the nature of the problem, you can change the position of the increment statement and the condition for the loop to terminate.
All for loops can be translated to while loops but not all while loops could be translated to a for loop. The main difference between the for loop and while loop is that the number of iterations to be carried out in a for loop is predetermined where as that is not the case in a while loop.
We could write all the for-loops above as while loops. Lets take a look at the factorial function
def factorial2(n):
= 1
results while n > 1:
*= n
results -= 1
n return results
6) factorial2(
720
An example where a for loop would not work:
Consider the following operation on an arbitrary positive integer:
If the number is even, divide it by two.
If the number is odd, triple it and add one.
In modular arithmetic notation, define the function f as follows:
\[ {\displaystyle f(n)={\begin{cases}n/2&{\text{if }}n\equiv 0{\pmod {2}},\\[4px]3n+1&{\text{if }}n\equiv 1{\pmod {2}}.\end{cases}}} \]
The question then becomes, given \(n\) list all the intermediate numbers until you get to 1.
For instance, starting with \(n = 12\) and applying the function \(f\) , one gets the sequence \(12, 6, 3, 10, 5, 16, 8, 4, 2, 1\)
Since the number of iterations is not known beforehand, we cannot use a for-loop. We are left to use a while-loop
:
def colletz(n):
= [n]
results while n > 1:
= n//2 if n % 2 == 0 else 3*n + 1
n
results.append(n)return results
12) colletz(
[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]
19) colletz(
[19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
Note that different n
takes different number of iterations. For example \(n = 12\) takes 9 iterations while \(n=27\) takes \(111\) iterations.
There are two ways to terminate a loop early:
continue
Exits the current iteration and continues to the next iteration:
for i in range(5):
if i == 3: continue
print(i)
0
1
2
4
Notice how the number 3 was not printed. That is because at i=3
we skipped the iteration and went to the next iteration. Often this code is not used as it wastes resources. But in case you find yourself not being able to avoid it, then go ahead and use it.
break
Exits the entire for/while
loop.
= 3
x while x<10:
print(x)
if x == 7: break
+= 1 x
3
4
5
6
7
What really is the benefit of break? Note that in the above example, we can just to a for loop of elements from in range(3, 8)
. Although it depicts the picture of what break
does, it don not capture the whole essence.
Suppose we want to compute the value of \(x\) such that \(x^3 = 27\). How will we go about it?
Assume we are given that the update to the problem is \(x_{n+1} = \frac{x_n^3 + 27}{2x_n^2}\)
of course we know that the solution of the equation is 3. ie \(3^3=27\) . But we could use iterative methods with the given updating formula to solve the problem. Below is how the problem will look like
= 10 #initial guess value. We assume that 10 is the solution. Then solve from there
x while True:
= (x**3 + 27) /(2 * x**2)
x_new if abs(x_new - x) < 1e-5: break
= x_new
x print(x)
5.135
3.0794798545408346
2.963310604450811
3.019028899041687
2.990665080839297
3.0047112114109655
2.9976554688592474
3.001175016850196
2.9994131815468656
3.000293581449432
2.9998532523646957
3.0000733845857885
2.999963310399667
3.000018345473241
2.9999908274316565
3.00000458632624
Note that we are interested in ensuring that the absolute difference between \(x\) and the \(x_{new}\) is less than some very small number \(\epsilon = 10^5\). In most cases this is considered to be the tolerance level of the error. The smaller the number, the more accurate our results will be. In this case, we get some number close to \(3\) but not quite enough. Lets change that:
= 10
x while True:
= (x**3 + 27) /(2 * x**2)
x_new if abs(x_new - x) < 1e-16: break
= x_new
x x
3.0
Here we see that the results is exactly 3. This is due to the small tolerance level used. ie \(10^{-16}\) .
The while loop depicted above, without the break condition, is infinite. That is because its initial condition ie while True
will always be TRUE. We thus need a condition to exit the loop. We provide the condition using an if
statement and then use the break
statement to exit the loop.
The same code above could be written in different ways:
= 10
x = 1
err while err > 1e-16:
= (x**3 + 27) /(2 * x**2)
x_new = abs(x_new - x)
err = x_new
x x
3.0
A very unconventional way to do the same is by using the assignment expression operator :=
= 10
x = 1
err while err > 1e-16:
= abs(x - (x := (x**3 + 27) /(2 * x**2)))
err x
3.0
= 10
x while True:
if abs(x - (x := (x**3 + 27) /(2 * x**2))) < 1e-16: break
x
3.0
= 10
x while abs(x - (x := (x**3 + 27) /(2 * x**2))) > 1e-16: pass
x
3.0
NB: The update given above is problem specific. To learn how to get the updates look at root finding methods
def cumsum(seq): # cummulative sum of a sequence.
= [0]
result for i in seq:
-1] + i)
result.append(result[return result[1:]
range(1, 11)) cumsum(
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
def cumsum2(seq):
= [seq[0]]
result for i in seq[1:]:
-1] + i)
result.append(result[return result
range(1, 11)) cumsum2(
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
These characters enable one to neatly print elements on the screen.
\b backspace- Deletes one element to the left- Moves cursor one element to the left
print("This iss\b good") # One s has been deleted
This iss good
\n new line/line feed - prints the next text in a new line. Moves cursor to a new line.
print("First line\nSecond Line\n\nThiiird line")
First line
Second Line
Thiiird line
\r carriage return - Moves cursor to the begining of the current line
print("This replaced by \rthat \n")
This replaced by
that
\t tab - Moves cursor 4-spaces ahead
print("Hello\tWorld\n")
Hello World
Multiplication Table.
print("\t------------------------------------------")
------------------------------------------
print("\t1\t 2\t 3\t 4\t 5\t 6")
1 2 3 4 5 6
print("\t------------------------------------------")
------------------------------------------
for i in range(1, 7):
print("\t", i, end='')
for j in range(1, 7):
print("\t", j * i, end='')
print()
1 1 2 3 4 5 6
2 2 4 6 8 10 12
3 3 6 9 12 15 18
4 4 8 12 16 20 24
5 5 10 15 20 25 30
6 6 12 18 24 30 36
Of course we can control the number of digits/ the width of the text to be printed using sprintf
function.
Notice how I used extra curly braces {}
to wrap the whole code as one. By removing the outermost braces, the behavior changes. The code needs to be run as a single unit and that’s why we put the extra braces
Submit work here
Write a function that prints a multiplication table. Use double for-loops:
In exercise 1, we had used various functions to write a my_max
function. Having learned about control flows, write a function to compute the maximum of two numbers.
Write a function grade(x)
that would take in the marks of a student and output the grade as per the table below eg grade(73)
should output C
A | B | C | D | E | F |
---|---|---|---|---|---|
90-100 | 80-89 | 70-79 | 60-69 | 50-59 | <50 |
Write a function grades(x)
that would take in a list/tuple of scores and output the grades as per the table in question one.
Input: [73, 92, 80, 49]
Output: [C, A, B, F]
Write a program parse_number
that takes in a string and returns a float number from it. If the string is not a valid number, the function should return nan
which is a special placeholder for a float that is not a number. The nan
can be obtained by float('nan')
. The program should not throw an error. To accomplish the task, you have to ensure that the string contains at most one period and also that apart from the period (if any), everything else is numeric.
Examples:
Input: '1234'
Output: 1234.0
Input: '1234.5'
Output: 1234.5
Input: '1234.5.6'
Output: nan
Input: '12a4'
Output: nan
Input: 'This is not right'
Output: nan
globals()
function returns all the variables currently present in your work space/environment. Using a dict comprehension, filter and output only the variables that do not begin with an underscore.
Example
a = 1
b = 3
d = range(5)
Output: {'a':1, 'b':3, 'd':range(5)}
dir(str)
outputs all the methods in the str
class. List all methods accessible to the user. ie methods that do not start with an underscore. Output should be similar to the list shown in introduction page on str methods
\(\mathbf{\pi}\) Estimation: Using the formula: \(\pi = 4 - \frac{4}{3} + \frac{4}{5} - \frac{4}{7} - \frac{4}{9}+\cdots\) use the first 100,000 terms to estimate \(\pi\). Does it converge?
Hint: \(\pi = 4\sum_{i=1}^n \frac{(-1)^{i-1}}{2i-1}\)
The sequence above converges slowly. For faster convergence we use Ramanujan’s equation for \(\pi\) ie: \({\frac {1}{\pi }}={\frac {2{\sqrt {2}}}{9801}}\sum _{k=0}^{\infty }{\frac {(4k)!(1103+26390k)}{(k!)^{4}396^{4k}}}\) . Use the first 5 terms to estimate \(\pi\) . You can print more decimals on the screen by using print(f'{pi:.22f}')
or using . (You should watch the movie - “The man who knew Infinity”)
Square root -Average Method: To compute the square root of a number, one can often use an iterative approach of line search.
Assume we want the \(\sqrt{2}\). Let our initial points be 0 and 2, then we could do:
\[ \begin{aligned} \operatorname{begin} &= 0, \quad\operatorname{end}=2\\ x_1 &= \frac{0 + 2}{2} = 1 ~~ie~~ \text{midpoint between }0 \text{ and }2 \\ \operatorname{error} &= 2-1 = 1\\ \text{Since error}&>0, \operatorname{begin=}1\quad x_2 = \frac{1+2}{2} = 1.5 ~~ie~~ \text{midpoint between }1 \text{ and }2 \\ \operatorname{error} &= 2-1.5^2 = -0.25\\ \text{Since error}&<0, \operatorname{end=}1.5\quad x_3 = \frac{1+1.5}{2} = 1.25\\ \operatorname{error} &= 2-1.25^2 = 0.4375\\ \text{Since error}&>0, \operatorname{begin=}1.25\\ x_4 &= \frac{1.25+1.5}{2} = 1.375\\ \vdots \end{aligned} \]
This algorithm converges slowly. We continue averaging until the \(|\epsilon|\leq\tau\) where \(\tau\) is taken to be a very small number for example \(\tau =10^{-8}\) =1e-8
. Write a function sqrt_avg(x)
implementing the algorithm above.
Square root - Heron’s Method-: To calculate \(\sqrt{S}\) we use the equation
\[
x_{n+1} = \frac{1}{2}\left(x_n + \frac{S}{x_n}\right)
\]
where \(x_0\) is any arbitrary positive integer. The closer it is to \(\sqrt{S}\) the better.
Given \(S=125348\) ,we can estimate to six significant figures the \(\sqrt{S}\) . using the rough estimation method above to get
\[ \begin{array}{ll}x_0=6 \cdot 10^2 & =600.000 \\x_1=\frac{1}{2}\left(x_0+\frac{S}{x_0}\right)=\frac{1}{2}\left(600.000+\frac{125348}{600.000}\right)=404.457 \\x_2=\frac{1}{2}\left(x_1+\frac{S}{x_1}\right)=\frac{1}{2}\left(404.457+\frac{125348}{404.457}\right)=357.187 \\x_3=\frac{1}{2}\left(x_2+\frac{S}{x_2}\right)=\frac{1}{2}\left(357.187+\frac{125348}{357.187}\right)=354.059 \\x_4=\frac{1}{2}\left(x_3+\frac{S}{x_3}\right)=\frac{1}{2}\left(354.059+\frac{125348}{354.059}\right)=354.045 \\x_5=\frac{1}{2}\left(x_4+\frac{S}{x_4}\right)=\frac{1}{2}\left(354.045+\frac{125348}{354.045}\right)=354.045\end{array}\\ \therefore \sqrt{125348} \approx 354.045 \]
Note that \(x_0\) does not necessarily have to be the number given above. It can be any positive number eg 1. Using the algorithm described above, write a function named sqrt_heron(x)
that takes in one parameter and computes the square root of any non-negative real number. Use a tolerance level of 1e-8
Reverse Integer: Given a signed 32-bit integer x
, return x
with its digits reversed. If reversing x
causes the value to go outside the signed 32-bit integer range \([-2^{31}, 2^{31} - 1]\) then return 0
.
Input: x = 123
Output: 321
Input: x = -123
Output: -321
Input: x = 120
Output: 21
Longest Sub-string: Given a string s
, find the longest sub string without repeating characters. Note that this is abbit different from the original question in that we return the sub string rather than the length of the sub string.
Input: s = "abcabcbb"
Output: "abc"
Input: s = "bbbbb"
Output: "b"
Input: s = "pwwkew"
Output: "wke"
Input: s = "pwwkewesdal"
Output: "wesdal""