import numpy as np
import matplotlib.pyplot as plt
%config InlineBackend.figure_format = 'retina'
%matplotlib inline
plt.rcParams.update({'font.size':16})
In this section we'll be revisiting functions and pseudocode. If you need a refresher on these take a look back at worksheets C4 and C5 from semester 1.
On worksheet C4 the final exercise was to write an algorithm to determine the first 20 values of the Fibonacci sequence using pseudocode, then convert your psuedocode to a script. We're going to take another look at this exercise. The difference is that this time we'll be writing our psuedocode in the notebook.
The fist thing to do is try to write down what's happening when we're calculating the values of the Fibonacci sequence:
1. first value is 1
2. second value is 1
3. next value is value[next] + value[next - 1]
* need something to deal with the first value
* need something to count the number of values
The pseudocode above will get us most of the way there. To get a useful function we're going to need some way of keeping track of how many times we've run the code, and to deal with the start of the function (i.e. setting the first and second values to 1). This is what the last two lines in the pseudocode deal with.
Remember, your pseudocode doesn't need to be perfect. It needs to be written in a way that you understand, and (ideally) so that someone who may not know the programming language you're using could interpret it.
Now we know what we want the code to do we can start turning it into Python code. Line 3 of the pseudocode
3. next value is value[next] + value[next - 1]
tells us that we're going to need a loop in here somewhere. The first two lines tell us that we could use an if
statement.
We want to print the first 20 values in the sequence, so the first thing to do is set up an array to hold those values. We do that with the np.zeros()
function. This creates an array with 20 elements all set to zero.
We then want to fill the array using a for
loop. To keep track of the number of iterations we can use
for n in range(20):
The range
function is very useful for loops.
The final step is the if
statement. We know the first two values are 1, so we should include an if
statement to deal with those. We can use an else
statement to deal with the rest of the values.
fibonacci = np.zeros(???)
for n in range(???):
if n < ???:
fibonacci[n] = 1
else:
fibonacci[n] = ???
print(fibonacci)
If you've completed Exercise 3 (or looked at the solution...) you'll see that the printed output isn't particularly pretty. When we print the fibonacci
array we just get an array of floats printed to the screen. We can tidy that up with formatted print statements. We covered this a little on the previous page when we were making nicely formatted plot titles. We'll now look at formatting in more detail.
Printing the fibonacci
array like we did in the exercise above is useful for having a quick look at what the result looks like. But often we want to have the output formatted in a more readable fashion. In the previous section of this lesson we used string formatting to make the plot titles more useful. The same principles apply to any string formatting.
For this particular case we know that all our values are integer values. We can change our print
statement to print them as integers rather than floats using astype(int)
:
print(fibonacci.astype(int))
We can do a similar thing inside our loop to create a table of the Fibonacci values. Here we're using the {x:>10d}
notation to tell it to print each value in a right justified column 10 characters wide.
print('{:>10} {:>10}'.format('n', 'Fibonacci'))
for n in range(20):
print("{0:>10d} {1:>10d}".format(n+1, fibonacci[n].astype(int)))
If we wanted to left justify our columns we can just switch the {x:>10d}
to {x:<10d}
.
Where this becomes more important is when we're writing to a file. We could just write the output as it is above to a file, but what if we had some more complicated data, or data with missing values? If we were relying on the spaces between the columns to tell our code which values correspond to each variable then we're going to run into problems.
Now that we have some code that we know works for calculating the Fibonacci sequence and writing the results to a file we can put it all together in a function. You've come across functions before, in semester 1 and in the previous section of this lesson when you defined a function to fit a straight line.
Here we want to define a function that will calculate the first n
terms of the Fibonacci sequence and save them to a file.
When we define a function we tell it what arguments to expect in the parentheses on the def
line (I nearly wrote brackets here, but Dr Davies reminded us all in Week 2's Zoom session that these are not brackets, they are parentheses...). Here n_terms
is the number of terms we want and filename
is the filename (obvs).
def fib_fn(n_terms, filename):
fibonacci = np.zeros(n_terms)
for n in range(n_terms):
if n < 2:
fibonacci[n] = 1
else:
fibonacci[n] = fibonacci[n-2] + fibonacci[n-1]
n = np.arange(1,n_terms+1,1) ## getting our array so we can print the n values
np.savetxt(filename, np.column_stack([n, fibonacci]), fmt="%10d", header='{:>10}, {:>10}'.format('n', 'Fibonacci'), delimiter=',')
return(n, fibonacci)
Take note of the last line of the function:
return(n, fibonacci)
This is a return value. Every time the function runs it will return the arrays n
and fibonacci
. This is useful - we can see what the result was without looking in the file, either by just running the function like below:
fib_fn(10, 'fibonacci_10.txt')
Or by sending the output to some new variables (a bit like we do with np.loadtxt
):
fn_n, fn_fib = fib_fn(10, 'fibonacci_10.txt')
print(fn_n)
print(fn_fib)
The final thing we can do to make this function more useful is to set some default values for the arguments. These are the values that will be used if we don't specify anthing. We're not going to set a default value for n_terms
as we always want to specify that, but we can give it a default filename.
def fib_fn(n_terms, filename='fibonacci_results.txt'):
fibonacci = np.zeros(n_terms)
for n in range(n_terms):
if n < 2:
fibonacci[n] = 1
else:
fibonacci[n] = fibonacci[n-2] + fibonacci[n-1]
n = np.arange(1,n_terms+1,1) ## getting our array so we can print the n values
np.savetxt(filename, np.column_stack([n, fibonacci]), fmt="%10d", header='{:>10}, {:>10}'.format('n', 'Fibonacci'), delimiter=',')
return(n, fibonacci)
Now if we run the function and only specify n_terms
, the function will still run and it will save the results to fibonacci_results.txt
. Be careful though! You won't get a warning if the file exists already - it will just get overwritten.
fib_fn(5)