In [1]:
import numpy as np
import matplotlib.pyplot as plt
In [2]:
%config InlineBackend.figure_format = 'retina'
%matplotlib inline
In [3]:
plt.rcParams.update({'font.size':16}) 

Functions and Pseudocode

Overview:

Questions

  • How do you write pseudocode in notebooks?
  • How do you write functions in notebooks?
  • How do you set the default values in functions?

Objectives

  • Write some pseudocode
  • Write a function
  • Set the default values for the function

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.

Fibonacci sequence

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:

Algorithm

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.

Turning pseudocode into code

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.

Exercise 2

Check that you understand what the range function is doing. In an empty cell in your notebook write some code to print

  1. what range(20) returns
  2. what n returns in a for n in range(20) loop

solution

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.

Exercise 3

Copy the code in the next cell into your notebook. Edit the code so that it prints the first 20 values of the Fibonacci sequence.

You'll need to replace the ???s in the code with the correct values or mathematics.

solution

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.

Formatted print statements

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):

In [7]:
print(fibonacci.astype(int))
[   1    1    2    3    5    8   13   21   34   55   89  144  233  377
  610  987 1597 2584 4181 6765]

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.

In [8]:
print('{:>10} {:>10}'.format('n', 'Fibonacci'))
for n in range(20):
    print("{0:>10d} {1:>10d}".format(n+1, fibonacci[n].astype(int)))
         n  Fibonacci
         1          1
         2          1
         3          2
         4          3
         5          5
         6          8
         7         13
         8         21
         9         34
        10         55
        11         89
        12        144
        13        233
        14        377
        15        610
        16        987
        17       1597
        18       2584
        19       4181
        20       6765

If we wanted to left justify our columns we can just switch the {x:>10d} to {x:<10d}.

Python documentation

If you want to look at the whole variety of string formatting options available, go to the Python 'Input and Output' documentation.

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.

Exercise 4

In the C5 worksheet we used np.loadtxt and np.savetxt. Use np.savetxt to save the output of your Fibonacci code to a text file. Format the output so each column has a heading and the columns are separated by commas.

Hint: Remind yourself about the delimeter argument for np.savetxt. You can see information about all the arguments for this function here.

Once you've written the results to a file, use np.loadtxt to read it back in. Check that you get back what you expect.

solution

Columns

In Exercise 4 we used list(zip(n, fibonacci)) to get np.savetxt to print our arrays as columns in the file. Another option is to use np.column_stack([n, fibonacci]). Both do the same thing, but I find column_stack() a little more intuitive (and easier to remember).

Put it all in a function

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).

In [12]:
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:

In [13]:
fib_fn(10, 'fibonacci_10.txt')
Out[13]:
(array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10]),
 array([ 1.,  1.,  2.,  3.,  5.,  8., 13., 21., 34., 55.]))

Or by sending the output to some new variables (a bit like we do with np.loadtxt):

In [14]:
fn_n, fn_fib = fib_fn(10, 'fibonacci_10.txt')
print(fn_n)
print(fn_fib)
[ 1  2  3  4  5  6  7  8  9 10]
[ 1.  1.  2.  3.  5.  8. 13. 21. 34. 55.]

Default values

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.

In [15]:
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.

In [16]:
fib_fn(5)
Out[16]:
(array([1, 2, 3, 4, 5]), array([1., 1., 2., 3., 5.]))

Key Points:

  • Write out your programs in pseudocode before diving straight into Python to help you figure out the logic your code needs to follow.
  • You can (and should!) use the markdown cells in your notebook to write what your code is doing and why it's doing it.
  • Break your code down into individual steps before you start to put it all together in a function - it's much easier to spot problems that way.
  • Default values for function arguments can be set when you define your function.