Yes it is possible, and often it is useful. Say we have the function
def evaluateAFunctionAt2(f):
print ("The value of this function at x=2 is ", f(2))
Which takes a function and evaluates it at 2. If we want to use it for a family of functions $f(x)=x^i$ we can use this loop:
for exponent in range(1,10):
def theFunction(x):
return x**exponent
evaluateAFunctionAt2(theFunction)
The value of this function at x=2 is 2
The value of this function at x=2 is 4
The value of this function at x=2 is 8
The value of this function at x=2 is 16
The value of this function at x=2 is 32
The value of this function at x=2 is 64
The value of this function at x=2 is 128
The value of this function at x=2 is 256
The value of this function at x=2 is 512
If we have two arrays:
import numpy
a = numpy.array([1.0, 2.0, 3.0])
b = numpy.array([4.0 ,5.0 ,6.0])
and we want to combine them into a single array, we have (at least) two options. The first is to create an array to contain the combination and fill it using indexing:
combined = numpy.empty(6)
combined[:3] = a
combined[3:] = b
print(combined)
[1. 2. 3. 4. 5. 6.]
Another option is to use numpy.concatenate
:
combined = numpy.concatenate([a,b])
print(combined)
[1. 2. 3. 4. 5. 6.]
numpy.concatenate
has more options to combine more structured arrays.
Tuple unpacking refers to the ability of assigning several values in one assignment. For example in
a, b, c = 1, 2, 3
a, b, c = ['a', 'b', 'c']
we can set a
, b
, c
at once. Tuple unpacking is especially useful when looping over lists. Say we have a list of restaurant orders:
orders = [
('Adam', 'Pizza funghi', 7.99),
('Sarah', 'Gnocchi al pesto', 8.59),
('Zoe', 'Spaghetti al arabiata', 6.55)
]
We can loop over the orders and index into each order to access the name, meal and price
total = 0
for order in orders:
total += order[2]
print ("{} ordered {}".format(order[0], order[1]) )
but we can make the code a lot more explicit using tuple unpacking:
total = 0
for name, meal, price in orders:
total += price
print ("{} ordered {}".format(name, meal) )
print("Total bill: {}".format(total))
Adam ordered Pizza funghi
Sarah ordered Gnocchi al pesto
Zoe ordered Spaghetti al arabiata
Total bill: 23.13
To get feedback go to the assignment tab, the assignment for which feedback is available should have a “fetch feedback” button. Pressing this will copy the feedback to your server. Once this is done you can view the feedback by clicking on the “view” link.
To create a loop where one is not sure how many iterations will be necessary one can use a while
loop.
We can use While True
to repeat indefinitely, but we have to escape the loop according to some criteria. There are two ways of escaping: break
or return
if inside a function.
Using break
:
i = 0
while True:
if i == 17:
break
i += 1
i
17
Using return
in a function body:
def doForAWhile():
i = 0
while True:
if i == 17:
return i
i += 1
doForAWhile()
17
The criterium can also be in the while
statement:
i = 0
while i < 234:
i += 1
i
234
With infinite loops there is a danger: they can actually go infinite, that is if we miss the termination condition the computer will never finish executing the code. If this happens in a notebook the execution can be stopped using the square icon in the notebook toolbar, or use the “Kernel-interrupt” menu.
Sometimes the infinite loop uses more and more resources, for example if elements are added to an array in the loop and the size of the array gets out of control. For example here:
aList = []
i = 0
while True:
aList.append(i)
i += 1
You might get a “Kernel restarting” error, saying “The kernel appears to have died. It will restart automatically.” THis happens because the memory available to your server has been exhausted. In accute cases the notebook might become unresponsive. The best way to solve the problem is to close the tab and to go to the “Running” tab in the home page and click the “shutdown” button.
With all these issues it is really worth being preventive with infinite loops. One way is to stop the loop after a large number of iteration, when it becomes clear something went wrong:
def runCodeRun():
x = 0.0
nIterations = 0
while nIterations < 100000 :
x += 0.1
if x == 123.6:
return "Victory!"
nIterations += 1
# if I get there it means the loop did not end after 100000 iterations
return "Oops, the loop never ended..."
runCodeRun()
'Oops, the loop never ended...'
This code should work but we got into trouble with numerical accuracy, we can fix it by using inequalities rather than strict equalities. Having the safeguard with nIteration
allowed us to diagnose the problem immediately and not have to stare at the notebook for ages while we wonder why the code never finished.
def runCodeRun():
x = 0.0
nIterations = 0
while nIterations < 100000 :
x += 0.1
if x >= 123.6:
return "Victory!"
nIterations += 1
# if I get there it means the loop did not end after 100000 iterations
return "Oops, the loop never ended..."
runCodeRun()
'Victory!'
If we are not within a function we can use while
, break
, else
to achieve the same effect.
x = 0.0
nIterations = 0
while nIterations < 100000 :
x += 0.1
if x == 123.6:
print ("Victory!")
break
nIterations += 1
else:
print("Oops, the loop never ended...")
Oops, the loop never ended...
This can be fixed again using inequalities:
x = 0.0
nIterations = 0
while nIterations < 100000 :
x += 0.1
if x >= 123.6:
print ("Victory!")
break
nIterations += 1
else:
print("Oops, the loop never ended...")
Victory!
When you press the “submit” button a new line should appear in the list of your submissions with the time of the submission (the time is in UTC and might be off by one hour depending on the timezone).
If you went to the submission page and left it for more than an hour it can happen that your srever was stopped and the page you see in your browser is not linking to it any longer. The symptom of that is that when you submit no new line is added and you might get an error message. If this happens you have to log back in and submit. Clicking on the “Files” tab should restart your server and get you ready to submit.
If your notebook has too much output (typically if you use print statements in a large loop) your notebook will refuse to save. You would get a message saying “request entity too large” or you might see “autosave failed!” messages. Remove the offending print function calls and rerun the cells to be able to save the notebook. You could also use “Restart and clear all output” for the Kernel menu to remove all output. You will still need to remove the print calls but this should allow you to save your work first.
The marking works in the following way:
This means that you have to make sure your notebook produces all plots you want to have in your assignment. You can see what would be the result of running your notebook like the marking system does by selecting “Restart & Run all” in the “Kernel menu”.
If you plotted some curve and you cannot see it there could be a few problems:
if your plot is a log plot and your curve has negative values they are not shown. Print the values you want the plot to check they are not negative!
if you have some hard-coded limits through plt.xlim
or plt.ylim
your curve might be outside of that range. It can help to print the values you want to plot, or to temporarily disable the limits to check where your curves are.
if you have several curves but only see one: they might be on top of each other!
With np.zeros
you create an array and wet all of its elements to 0. The time spent setting all elements to zero can be a waste of time if you never use the 0 values and fill the array with different values later:
a = numpy.zeros(10)
for i in range(10):
a[i] = 1.0 + i
There was no point spending time setting all elements to 0, and then set them to something else. This is why np.empty
is for: it creates the array but does not initialise the elements. This means the what the actual values are are quite random, if you use them you will get unpredictable results. It is perfectly safe to use if you are initialising all values of the array before using them. THe code below will have the same effect as the code above but will be quicker since we are not writing 0s in hte array before writing the values we really want.
a = numpy.empty(10)
for i in range(10):
a[i] = 1.0 + i
You can use list comprehentions:
[2**n for n in range(20)]
[1,
2,
4,
8,
16,
32,
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
65536,
131072,
262144,
524288]
[10**(expo/10) for expo in range(100) ]
[1.0,
1.2589254117941673,
1.5848931924611136,
1.9952623149688795,
2.51188643150958,
3.1622776601683795,
3.9810717055349722,
5.011872336272722,
6.309573444801933,
7.943282347242816,
10.0,
12.589254117941675,
...
794328234.7242821,
1000000000.0,
1258925411.794166,
1584893192.4611108,
1995262314.9688828,
2511886431.509582,
3162277660.1683793,
3981071705.5349693,
5011872336.272715,
6309573444.801943,
7943282347.242822]
The numpy.logspace
function can be used for this. Be careful, the arguments are the minimum and maximum exponent!
numpy.logspace(1,6)
array([1.00000000e+01, 1.26485522e+01, 1.59985872e+01, 2.02358965e+01,
2.55954792e+01, 3.23745754e+01, 4.09491506e+01, 5.17947468e+01,
6.55128557e+01, 8.28642773e+01, 1.04811313e+02, 1.32571137e+02,
1.67683294e+02, 2.12095089e+02, 2.68269580e+02, 3.39322177e+02,
4.29193426e+02, 5.42867544e+02, 6.86648845e+02, 8.68511374e+02,
1.09854114e+03, 1.38949549e+03, 1.75751062e+03, 2.22299648e+03,
2.81176870e+03, 3.55648031e+03, 4.49843267e+03, 5.68986603e+03,
7.19685673e+03, 9.10298178e+03, 1.15139540e+04, 1.45634848e+04,
1.84206997e+04, 2.32995181e+04, 2.94705170e+04, 3.72759372e+04,
4.71486636e+04, 5.96362332e+04, 7.54312006e+04, 9.54095476e+04,
1.20679264e+05, 1.52641797e+05, 1.93069773e+05, 2.44205309e+05,
3.08884360e+05, 3.90693994e+05, 4.94171336e+05, 6.25055193e+05,
7.90604321e+05, 1.00000000e+06])
To loop over the elements of an array we can loop over an index that goes from 0 to the lenght of the array:
allElements = [1,2,3,7,99]
for i in range(len(allElements)):
print(allElements[i])
1
2
3
7
99
This is useful if you need i
for other purposes, such as accessing values of the array at different places. If you do not need i
but are only interested in the element itself you can use the following Python syntax:
allElements = [1,2,3,7,99]
for oneElement in allElements:
print(oneElement)
1
2
3
7
99
There are many ways to construct a list in python, I list here a few. In each example we will construct the set of the ten first odd numbers.
You can create a new list and fill it in the for
loop.
l = []
for i in range(10):
o = 2 * i +1
l.append(o)
print(l)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
alternatively you can fill an existing list with the elements:
l = [0]*10
for i in range(10):
o = 2 * i +1
l[i] = o
print(l)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Because creating lists is so common python has a dedicated syntax for it called list comprehension, here is an example.
l = [ 2*i + 1 for i in range(10) ]
print(l)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
numpy
This is the same as the for loop above, but with a numpy array instead of a list. Note that the default for numpy arrays is floats, so if you want integers you have to specify dtype=int
when you create the array.
import numpy
l = numpy.zeros(10, dtype=int)
for i in range(10):
l[i] = 2*i + 1
print(l)
[ 1 3 5 7 9 11 13 15 17 19]
For simple intervals you can use the numpy.linspace
function:
l = numpy.linspace(1, 19, 10, dtype=int)
print(l)
array([ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19])
For simple operations you can manipulate numpy arrays to obtain new ones:
all_i = numpy.array(range(10))
l = 2 * all_i +1
print(l)
[ 1 3 5 7 9 11 13 15 17 19]
This is the same as above, but using a function instead.
all_i = numpy.array(range(10))
def odd(i):
return 2*i + 1
l = odd(all_i)
print(l)
[ 1 3 5 7 9 11 13 15 17 19]
The two method above require the function to be simple enough for numpy to understand it, that means, among other that mathematical functions such as sin
or sqrt
are imported from the numpy
library and not from the math
library.
You can’t… This is by design: the assert cells are there to test your work and are part of the assessment. If you want to add more tests to help you debug you can create a new cell using the “+” button in the toolbar at the top of the notebook. The cells you create can be edited any way you want.
Yes, the assignments are summative. They account for 17% of the module mark.
No, there is no need to validate before submitting, but it can be useful to check that your notebook executes properly.
In a coding exercise the place you have to insert code in is marked by the following lines.
# YOUR CODE HERE
raise NotImplementedError()
Raising an exception everywhere you are supposed to contribute some code makes sure that you will not miss one. When you use “validate” you will be warned that you still need to do something there. Once you have written the code you can remove the raise
statement. If you return
before the raise
statement there is no need to remove the line for the test to run without error.