**Mar 8, 2018**

Python, like all computer languages, contains many built-in functions, such as `print()`

, `range()`

, `len()`

. These functions carry out commonly-used tasks. Because most programming problems have their own commonly-used tasks specific to the given problem, it is often necessary to write custom functions. In fact, functions are an integral part of programming, allowing your code to be modular, repeatable, and readable.

In Python, functions are defined with the `def`

command. Similar to `if`

and `for`

, code following the `def`

command must be indented. Functions generally take arguments as input and return results from the computation as output. To end the function and return a result, we use the command `return`

.

The basic format for defining a function is:

```
def my_function(argument1, argument2, ...):
code
code
more code
return x
```

Note that the variable names (above, `argument1`

and `argument2`

) are completely arbitrary and exist only within the code of the function itself.

Here is an example of a simple function to add two numbers together:

In [1]:

```
# Define a function called `add`.
# This function takes 2 arguments and returns their sum.
def add(x, y):
return x + y
# To execute the code inside the function, run the function:
print(add(4, 99))
# We can also use variables as input to the function:
number1 = 4
number2 = 99
print(add(number1, number2))
```

Variables that are defined inside of functions do not exist outside of the function. For example, in this case, the function `add`

defines a variable `result`

but this does not change the value of the variable `result`

outside of the function:

In [2]:

```
result = 15 # define variable `result`
def add(x, y): # define function `add`, which uses a variable `result`
result = x + y
return result
print(result) # result is 15
add(1, 2) # add numbers 1 and 2
print(result) # result still 15
```

Function arguments can have default values, in which case they don't need to be provided when the function is called:

In [3]:

```
# Use 0 as default value for y. If it is not given, the add
# function adds nothing:
def add(x, y = 0):
result = x + y
return result
print(add(5)) # 5
print(add(5, 2)) # 7
```

The order of arguments matters. Arguments need to be provided in the order in which they are listed in the function definition. As an example, consider a simple function that divides two numbers. Obviously, it matters which number is the numerator and which the denominator:

In [4]:

```
# Simple divide function
def divide(numerator, denominator):
return numerator / denominator
print(divide(10, 5)) # 10/5 = 2
print(divide(5, 10)) # 5/10 = 0.5
```

Functions can return more than one result at once. This is done by separating the results by commas in the `return`

statement:

In [5]:

```
# Define a function to divide two numbers.
# The function returns both the dividend and remainder:
def divide_remain(x, y):
div = x / y
rem = x % y
return div, rem # return multiple values separated by commas
# Run the function and save returned values
a = 88.0
b = 12.0
# Returned values can be saved to unique variables
ratio, remainder = divide_remain(a, b)
print(ratio)
print(remainder)
```

In [6]:

```
# Alternatively, returned values can be saved to a single variable
# and then indexed
results = divide_remain(a, b)
print(results[0])
print(results[1])
```

**Problem 1:**

Write a function to convert between degrees Celsius and Fahrenheit. The formulas are as follows:

`C = (F - 32) * 5/9`

`F = 9/5 C + 32`

(a) Write a function `convert_to_Celsius`

that takes a temperature in Fahrenheit as argument and returns the equivalent temperature in Celsius. Test that your function converts 32 F to 0 C and 212 F to 100 C.

(b) Now write a function `convert_temperature`

that can convert both Fahrenheit to Celsius and Celsius to Fahrenheit. This function should take two arguments, the temperature and the target unit (Celsius or Fahrenheit). Your function should return a single variable, the new converted temperature. Test that your function converts 0 C to 32 F and vice versa, and 100 C to 212 F and vice versa.

In [7]:

```
# Code for Problem 1a goes here.
def convert_to_Celsius(temp):
return (temp - 32.) * 5./9.
print("32 F in C:", convert_to_Celsius(32))
print("212 F in C:", convert_to_Celsius(212))
```

In [8]:

```
# Code for Problem 1b goes here.
def convert_temperature(temp, target_unit = 'f'):
# we first convert the unit string to lower case, so we
# need to test fewer cases in the following `if` statement
target_unit = target_unit.lower()
if target_unit == 'c' or target_unit == 'celsius':
return (temp - 32.) * 5./9.
else: # convert from C to F by default
return (9./5.) * temp + 32.
print("0C in F:", convert_temperature(0, "F"))
print("32F in C:", convert_temperature(32, "C"))
print("100C in F:", convert_temperature(100, "F"))
print("212F in C:", convert_temperature(212, "C"))
```

**Problem 2:**

This piece of code sums all multiples of 3 between 1 and 30:

```
result = 0 # our final result; we start with 0
for i in range(1, 31): # count from 1 to 30
if i%3 == 0: # test if number can be divided by 3
result += i # add number to result
print("The sum of all multiples of 3 which are <= 30 is", result)
```

Using this code as a template, write a function that can calculate all multiples of a given number, for all values within a specified range. This function should take three arguments: the multiple (3 in example above), the lower limit (1 in example above), and the upper limit (30 in example above). The function should return a single value, the sum of all multiples in that range.

In [9]:

```
# Code for Problem 2 goes here.
def sum_of_multiples(multiple, lower, upper):
result = 0
for i in range(lower, upper):
if i%multiple == 0:
result += i
return result
print(sum_of_multiples(3, 1, 31))
```

**Problem 3:**

a) Write a function called `calc_mol_weight`

to calculate the molecular weight of an amino acid sequence. Your function should take a single argument, an amino acid sequence, and return a single value, the sequence's weight. In your function, use the `amino_weights`

dictionary given below.

b) Test that your function behaves properly with the two `protseq`

variables defined below. The known molecular weights for each `protseq`

variable are shown in the comments.

In [10]:

```
# Your function goes here.
def calc_mol_weight(protseq):
amino_weights = {'A':89.09,'R':174.20,'N':132.12,'D':133.10,
'C':121.15,'Q':146.15,'E':147.13,'G':75.07,
'H':155.16,'I':131.17,'L':131.17,'K':146.19,
'M':149.21,'F':165.19,'P':115.13,'S':105.09,
'T':119.12,'W':204.23,'Y':181.19,'V':117.15}
weight = 0
for aa in protseq:
if aa in amino_weights:
weight += amino_weights[aa]
return weight
# Variables for testing that the function works properly.
protseq1 = "AGAHHCTPL" # weight = 1050.14
protseq2 = "HQWRSSXAD" # weight = 1112.11
# Run the function on protseq1 and protseq2.
print(calc_mol_weight(protseq1))
print(calc_mol_weight(protseq2))
```

**Problem 4:**

For Problem 3 above, write code that automatically tests whether the `calc_mol_weight`

function returns the correct result for `protseq1`

and `protseq2`

.

In [11]:

```
# this doesn't work because of rounding errors
print("Incorrect implementation of test:")
if calc_mol_weight(protseq1)==1050.14:
print(" result correct for protseq1")
else:
print(" result incorrect for protseq1")
if calc_mol_weight(protseq2)==1112.11:
print(" result correct for protseq1")
else:
print(" result incorrect for protseq2")
# we need to test whether they fall into a given small range of values:
print("Correct implementation of test:")
if abs(calc_mol_weight(protseq1)-1050.14) < 0.00001:
print(" result correct for protseq1")
else:
print(" result incorrect for protseq1")
if abs(calc_mol_weight(protseq2)-1112.11) < 0.00001:
print(" result correct for protseq1")
else:
print(" result incorrect for protseq2")
```

**Problem 5:**

The Fibonacci Sequence is the sequence of numbers, starting with 0 and 1, where the next number is the sum of the two previous numbers:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Mathematically, we can write the $i$th number $n_i$ as

$n_i = n_{i-1} + n_{i-2}$.

Implement a function that calculates the *i*th Fibonacci number. (Hint: this is easiest by making the function call itself to obtain the previous Fibonacci numbers. This programming style is called "recursion".) Then use this function to print out the first 20 Fibonacci numbers.

In [12]:

```
def fibonacci(i):
if i<=1: # the first Fibonacci number is 0. We use <= instead of == to guard against bad input (e.g., i=0.5)
return 0
if i<=2: # the second Fibonacci number is 1. We use <= instead of == for safety, as before
return 1
return fibonacci(i-1)+fibonacci(i-2)# the next Fibonacci number is the sum of the two previous ones.
# print the first 20 fibonacci numbers
for i in range(1, 20):
print(fibonacci(i))
```