Module 1.2 - Functions

Objectives

By the end of this module you will be able to:

  • Enhance your understanding of how to use def to write functions, then invoke them.
  • Write code with function definitions and invocations.
  • Write and debug code with functions
  • Explore the different ways in which parameters work.
  • Identify new syntactic elements related to the above.

1.2.0    Review

Before continuing it is essential to review functions as described in Module 0.3

What to review most carefully:

  • The notion of how execution starts outside a function, goes into a function, and comes back to where the function was invoked.
  • When we come back to where the function was invoked, we continue execution just after the invocation.

Exercise 1.2.1

Exercise 1.2.1

Review Module 0.3

1.2.1 Simple Function Example

Consider this program:

def increment_and_print(a):
    a = a + 1
    print(a)

i = 5
increment_and_print(i)

j = 6
increment_and_print(j)

Exercise 1.2.2

Exercise 1.2.2

Type up the above in func_example.py. Then, just before the j = 6 statement, print the value of i.

Explanation:

  • Let’s start by distinguishing between a function definition (which merely tells Python what the function is about), and invocation (which asks Python to execute the function at that moment):

function definition

  • A function invocation is also referred to as a function call.
  • Now let’s peer into what constitutes a definition:

function definition

  • In the above case, the function increment_and_print has only one parameter called a.
  • In the future, we’ll see that a function can have several parameters, separated by commas. For example
def increment_and_print(a, b, c):
  • Next, let’s examine how execution proceeds, starting with what happens when a function is invoked:

function call

  • Once execution is inside the function:

function call

  • Next, execution moves further into the second invocation:

function call

  • The code inside the function now executes (again):

function call

  • Did you notice that neither i nor j was affected by the incrementing of a? This is because the value in i was copied into the freshly-created a at the moment the function was invoked. Even though a got incremented, that did not affect i. Variables like a that appear in the parentheses of a function definition are called parameters.

  • What does a function do with its parameters? Think of the parameters as variables that can be used as regular variables for any purpose. For example, consider this program:

def print_from_one_to(a):
    print('Printing between 1 and ', a)
    for i in range(1, a+1):
        print(i)

print_from_one_to(5)
print_from_one_to(6)
  • Here, we used the parameter a in setting the upper limit of a for-loop.

  • Important: When a function is defined with a parameter, the intent is that some code outside the function will set the value of the parameter. Thus, it would be allowed but technically defeat the purpose to write:

func_example2a
def print_from_one_to(a): 
    a = 5
    print('Printing between 1 and ', a)
    for i in range(1, a+1):
        print(i)

print_from_one_to(5)
print_from_one_to(6)  

Yes, this runs, but the whole point is for some other code to tell the function: - Here is some value for a - The function will perform actions on that value of a - If we pass a different value of a, the function will do something different

And so when we write:

func_example2b
def print_from_one_to(a):
    print('Printing between 1 and ', a)
    for i in range(1, a+1):
        print(i)

print_from_one_to(5)   # We're telling the function that a = 5
print_from_one_to(6)   # We're now telling the function to use a = 6

Exercise 1.2.3

Exercise 1.2.3

What do each of the above two programs print? Type them up in func_example2a.py and func_example2b.py to find out.

Exercise 1.2.4

Exercise 1.2.4

In func_example3.py, fill in the code in the function below so that the output of the program is:

*****
***
*

The partially-written program:

def print_stars(n):
    # Write your function code here

print_stars(5)
print_stars(3)
print_stars(1)

1.2.2    Multiple Parameters

Remember Pythagoras? We know his famous result for right triangles:

\(a^2 + b^2 = c^2\)

A Pythagorean triple is any group of three integers where the squares of the first two add up to the square of the third: \(3^2 + 4^2 = 5^2\) .

We’ll now write code to check whether a trio of numbers is indeed a Pythagorean triple:

def check_pythagorean(a, b, c):
    if (a*a + b*b) == c*c:
        print('yes')
    else:
        print('no')

check_pythagorean(3, 4, 5)
check_pythagorean(5, 12, 13)
check_pythagorean(6, 8, 20)

Note: This time, we’ve defined a function that takes three parameters:

def check_pythagorean (a, b, c):

Notice the commas separating the three variables. Consider the first invocation:

check_pythagorean(3, 4, 5)

The invocation also uses commas to separate arguments. When the function runs, the first argument (3) becomes a, the second argument (4) becomes b, and the third argument (5) becomes c. This is based on the order of the arguments- their relative position- in both the function definition and the function call.

Exercise 1.2.5

Exercise 1.2.5

In func_example4.py, write a simple function to compute the sum but only of the positive parameters; if neither value is positive, print 0. Fill in the code in the function below so that the output of the program is:

5
3
2
0

The partially written program is:

def print_profit_total(a,b):
    # Write your function code here

print_profit_total(2, 3)
print_profit_total(-2, 3)
print_profit_total(2, -3)
print_profit_total(-2, -3)

1.2.3    Return Values

We have written functions that take values, do things and print. We get a whole new level of programming power, when functions can compute something and return something to the invoking code.

Here’s an example:

def padd(a, b):
    total = 0
    if a >= 0:
        total += a
    if b >= 0:
        total += b
    return total

x = padd(-5, 6)
print(x)

Exercise 1.2.6

Exercise 1.2.6

Type up the above in func_example5.py. Then try passing arguments (5,-6) instead of (-5,6). Then try (5,6).

  • First, execution begins after the function definition is read by Python:

  • Remember, in an assignment statement, the right side of the expression on Line 1 is executed first: padd(-5, 6). The result of invoking this function somehow results in x getting a value stored inside it. (We’ll see how, shortly.)

  • Next, execution goes into the function:

  • When the return statement executes, the value returned (the value of total ) gets copied into x

  • One way to think about it: When we see x = padd(-5, 6) then think of execution going to the function padd with arguments (-5, 6)
  • After it executes, it returns the value in one of its variables: in this case, 6
  • That, behind the scenes, replaces the function invocation: x = 6 And thus, the value 6 gets copied into x.

Exercise 1.2.7

Exercise 1.2.7

In func_example6.py, complete the code in sum_up_to() so that it computes and then returns the sum of numbers from 1 to n (inclusive of 1 and n).

def sum_up_to(n):
    # write your code here

result = sum_up_to(5)
print(result)            # should print 15
result = sum_up_to(10)
print(result)            # should print 55

Let’s return to our earlier example and use the padd() function in different ways:

def padd(a, b):
    print('Received values: ', a, b)
    total = 0
    if a >= 0:
        total += a
    if b >= 0:
        total += b
    return total

print(padd(-5, 6))                   
x = padd(padd(-5,6), 7)              
print(x)                             
print(padd(padd(-5,6), padd(5,-6)))

Exercise 1.2.8

Exercise 1.2.8

Type up the above in func_example7.py.

Let’s examine each of the last three statements:

The first one:

print(padd(-5, 6))

In this case: The invocation to padd() occurs first: (padd(-5, 6)

  • That function executes and returns 6. So, we can think of the result of that as: print(6) Which sends 6 to the print() function.

Next, consider:

x = padd(padd(-5,6), 7)

In this case: The innermost padd() is first invoked: padd(-5,6). The function returns 6, and so, we could think of the result as x = padd(6, 7)

  • This now results in another invocation to padd() with parameter values padd(6, 7).
  • The function now executes again and returns 13. The result is x =13

Finally, consider the third example:

print(padd(padd(-5,6), padd(5,-6)))

In this case, there are two innermost invocations: padd(-5, 6) and padd(5,-6).

  • The left argument, padd(-5,6), is called first, resulting in a return value of 6: print( padd(6, padd(5,-6) ) )
  • Then, the right argument, padd(5, -6) is called, resulting in a return value of 5: print( padd( 6, 5) )
  • Now, these results (6 and 5) are sent once again to the third invocation: print(padd( 6, 5 ) )
  • This returns 11. print(``)
  • And so 11 gets sent to print, which prints it.

1.2.4    Multiple Returns In A Function

Take a moment to go back up and quickly glance through the Pythagorean example.

Now consider this rewrite:

def check_pythagorean(a, b, c):
    if a*a + b*b == c*c:
        print("The 'if' condition is met")
        return 'yes'
    else:
        print("The 'if' condition is not met")
        return 'no'

result = check_pythagorean(3, 4, 5)
print(result)
result = check_pythagorean(5, 12, 13)
print(result)
result = check_pythagorean(6, 8, 20)
print(result)

Note how the strings in the print statements above contain single quotes (') in their output, and so the print functions’s argument (a string) is demarcated by double quotes (").

Exercise 1.2.9

Exercise 1.2.9

Type up the above in func_example8.py.

return statements

When a function executes a return statement, execution exits the function right then and there, even if there’s more code below. For example, in the first time check_pythagorean() is invoked:

Exercise 1.2.10

Exercise 1.2.10

In func_example9.py, complete the code below so that the function, when given three numbers, identifies the two larger ones and returns their sum.

def add_bigger_pair(a, b, c):
    # Write your code here:

print(add_bigger_pair(2,3,4))  # Should print 7
print(add_bigger_pair(2,3,1))  # Should print 5
print(add_bigger_pair(2,1,4))  # Should print 6

1.2.5 Parameter and Argument Names

Consider this example:

def subtract(a, b):
   c = b - a
   return c

x = 5
y = 6
z = subtract(x, y)

We refer to a and b as parameters in the definition of the function: def subtract(a,b): When the function is called, subtract(x, y): we use the term arguments for x and y.

The names given to parameters have no relation to the names used for arguments: In the above case:

Consider this variation:

def subtract(a, b):
   c = b - a
   return c

c = 5
a = 6
b = subtract(a, b)

This is valid code, but it is confusing! The a,b,c that are the parameter variables (inside the function) are different from the a,b,c variables below (outside the function). If you aren’t sure, it’s safest to use different names.

1.2.6 Using Parameter Variables

Generally, the purpose of parameter variables is this:

Consider this example:

def example_func(a, b):
    c = 2*a - b
    print(c)

example_func(3, 4)
x = 6
example_func(x, 8)

From the point of view of the function, example_func thinks “Someone is going to put values into my variables a and b, and then I’ll do stuff like calculate and print”. From the point of view of code that is using the function, as in

example_func(3, 4)

This is saying “we’ll set the function’s first parameter (a)to 3, and second parameter (b) to 4, and then let the function do its thing”. Now, functions can use their parameter variables just like any other variable and change their values, as in:

def change_vars(p, q):
    print(p)
    p = p + 3*q       # We're changing p here
    print(p)
    r = p + q
    print(r)

x = 6
y = 7
change_vars(x, y)

Because the value in x gets copied into the variable p, the value in x does not get changed even though the function changes the value in p.

Exercise 1.2.11

Exercise 1.2.11

Just by reading, can you tell what the above program prints? Implement in func_example10.py and confirm.

1.2.7 Functions and Lists

Can a function receive a list as parameter? Yes. Can it return a list? Yes. Is this useful? Yes.

Let’s start with a list as parameter:

def compute_total(B):
    total = 0
    for k in B:
        total += k
    return total

A = [1, 3, 5, 7]
t = compute_total(A)
print(t)
  • Trace the execution of the above program, including loop iterations.

Exercise 1.2.12

Exercise 1.2.12

Type up the above in func_example11.py, including a print(k) in the loop to confirm your trace.

Exercise 1.2.13

Exercise 1.2.13

In even_numbers.py,complete the code in the function below so that the even numbers in a list are printed out:

def print_even(A):
    # Insert your code here

A = [1, 3, 5, 6, 7, 8]
print_even(A)

Write the function so that the output is:

Found even: 6
Found even: 8

Recall how to use the remainder operator (%) from the previous module (conditionals and loops).

Next, let’s look at an example where a list is returned:

def build_odd_list(n):
    L = []
    for i in range(0, n):
        k = 2*i + 1
        L.append(k)
    return L

print(build_odd_list(5))
  • Try to trace through the above.

Exercise 1.2.14

Exercise 1.2.14

Type up in odd_list.py to confirm the trace. Include a print(k) in the loop after the append.

Execution begins with print(build_odd_list(5)) This results in:

function definition

Once the return occurs, we could think of this as: print( [1, 3, 5, 7, 9] ) This gets sent to print, which prints. One could make this more explicit:

function definition

Next, let’s look at a list example with multiple returns:

def find_first_negative(A):
    for k in A:
        if k < 0:
            # First try print(k) here
            return k
            # Then try print(k) here
    return 0

B = [1, 2, -3]
C = [1, 2, 3]

print(find_first_negative(B))
print(find_first_negative(C))

Exercise 1.2.15

Exercise 1.2.15

Before typing this up in first_negative.py to confirm, try to trace through the program.

  • Then, replace the first comment so that you print(k) inside the loop.

  • Then, move the print statement (still inside the loop) to after the return k statement and observe how the output changes.

Exercise 1.2.16

Exercise 1.2.16

In last_negative.py, modify the above program to find the last negative number (if one exists) in a list. So, after completing the code below

def find_last_negative(A):
    # Insert your code here

B = [1, 2, -3, 4, -5, 6]
print(find_last_negative(B))
C = [1, 2, 3, 4]
print(find_last_negative(C))

it should print

-5
0

1.2.8  Returns That Don’t Return Anything

Consider this program:

def print_first_negative(A):
    for k in A:
        if k < 0:
            print('Found: ',k)
            return 
    print('No negatives found')

B = [1, 2, -3]
print_first_negative(B)
C = [1, 2, 3]
print_first_negative(C)

Exercise 1.2.17

Exercise 1.2.17

Type up the above in first_negative2.py, execute and observe the output.

Let’s point out: Whenever a return statement is executed in a function, execution exits the function right away. When a return statement does not return a variable’s value, execution leaves the function but does not give a value back to the invoking code. Thus the return here:

def print_first_negative(A):
    for k in A:
        if k < 0:
            print('Found: ',k) 
            return
    print('No negatives found') 

does not return a value but merely causes execution to leave the function and go back to just after where the function was invoked. Although it’s not needed, one could return at the end of a non-value-returning function:

def print_first_negative(A):
    for k in A:
        if k < 0:
            print('Found: ',k) 
            return
    print('No negatives found') 
    return

Why do we have multiple return’s in a function? Why not always wait until execution reaches the end of a function?

  • It is very useful to be able to return from anywhere in a function’s code.
  • As soon as the function’s “job” is done (example: finding the first negative), we want to leave the function.

1.2.9    A Difference Between Lists and Other Parameters

Consider this program:

def swapint(a, b):
    temp = a
    a = b
    b = temp
    print(a, b)

x = 5
y = 6
swapint(x, y)
print(x, y)                      # Will this print 5 and 6, or 6 and 5?

Exercise 1.2.18

Exercise 1.2.18

Before typing up the above in swap_int.py, can you guess what the output will be?

Let’s explain:

  • Execution of the program begins with the line
x = 5
  • When the function is called soon after:
swapint(x, y)
  • Execution enters the function with the values in x and y copied into a and b:

function definition

  • temp is a temporary variable created inside of the function:

function definition

Then, by the time we reach the print statement:

function definition

The values in a and b do indeed get swapped, but this does not affect x and y because they are actually different variables.

Now, consider a similar program with lists:

def swap_list_first(A, B):
    temp = A[0]
    A[0] = B[0]
    B[0] = temp

X = [1, 3, 5, 7]
Y = [2, 4, 6]
swap_list_first(X, Y)
print(X, Y)

Exercise 1.2.19

Exercise 1.2.19

Before typing up the above in swap_list.py, can you guess what the output will be? Is it what you expected?

Let’s see what’s going on:

  • List variables are fundamentally different from numeric variables.
  • Think of a list variable has having a reference ID tothe actual list contents:
  • This is like an “address” in memory.
  • If you have this “address” you can go to the list and do things with it.
  • List variables actual store these addresses (which, interestingly, turn out to be numbers).
  • We will draw a conceptual diagram to highlight this point:

function definition

Thus, when the function starts execution, the “references” in X and Y are copied into A and B. This means A and B refer to the same list contents. So, A[0] is the same as X[0], for example.

Next, after executing the three lines inside the function but before returning:

function definition

Notice that temp is a regular integer.

Exercise 1.2.20

Exercise 1.2.20

Draw the above diagram after each line in the function, showing how the list contents and temp change with each line of execution.

The key takeaways:

  • When you send number variables to a function, they get copied, and so the function can’t “do” anything to the variables you present as arguments.
  • If you send a list, a function can change the contents. This means you need to be careful about intent when writing functions that involve larger entities like lists. (There are other such “grouped data” entities, called objects .)
  • If the intent is to change contents, that’s fine, we should know that.

Exercise 1.2.21

Exercise 1.2.21

Consider the following program:

def change_int(a):
    a = a + 1

def change_list(A):
    A[0] = A[0] + 1

x = 5
change_int(x)
print(x)

B = [1, 2, 3]
change_list(B)
print(B)

Trace through the above program, showing boxes for the different variables changing as each line executes. Show these diagrams at the start of each function’s execution and just before each function returns.

1.2.10    Calling Functions From Functions

The code inside functions can be like regular code that’s outside functions. In particular, code inside functions can call (invoke) other functions.

For example:

def increment(a):
    b = a + 1
    return b

def increment_twice(c):
    d = increment(c)
    e = increment(d)
    return e

x = 5
y = increment_twice(x)
print(y)

Note: The program starts execution at the line

x = 5
  • When increment_twice(x) is called, we enter the function increment_twice() with the variable c set to 5 (copied from x).
  • The next line, d = increment(c) results in a call to increment() with the value 5 copied into parameter a.
  • Whe code in increment() executes, resulting in 6 being returned. The returned value 6 is stored in d.
  • increment() is called again with the value in d (now 6) copied into a. The code in increment() executes, returning 7.
  • Execution continues in the increment_twice() function and the value 7 is stored in e.
  • Finally increment_twice() completes execution and returns the value in e, which is 7. This value is stored in y.
  • Execution continues from there to the print.

We can shorten the above code by writing:

def increment(a):
    return a + 1

def increment_twice(c):
    return increment(increment(c))

x = 5
y = increment_twice(x)
print(y)
  • Notice that one can return the result of an expression: return a + 1
  • In this case, the calculation is an arithmetic expression. One can also have the result of a function call itself be returned:
def increment_twice(c):
    return increment(increment(c))
  • Afterwards, it’s possible to do this:
print(increment(increment(c)))
  • Here, the result of the inner increment() call is sent again to increment()
  • In the same vein, we can, instead of printing, execute a return
return increment(increment(c))

You do not have to use such shortcuts. Some shortcuts are an advanced topic and may in fact make your code harder to read, and harder to fix mistakes in.

Exercise 1.2.22

Exercise 1.2.22

Consider the following program:

def decrement(a):
    return a - 1

def subtract(a, b):
    for i in range(0, a):
        b = decrement(b)
    return b

print(subtract(5, 9))

Trace the execution step by step, showing the changing contents of variables a and b.

Type this up in subtraction.py to verify.

1.2.11 Data Analysis

Armed with our new ability to work with functions and lists, we will see how easy it is to compute basic statistics with data.

Exercise 1.2.23

Exercise 1.2.23

Consider the following program:

def find_smallest(A):
    smallest = A[0]
    for k in A:
        if k < smallest:
            smallest = k
    return smallest

data = [-2.3, -1.22, 1.6, -0.5, 1.4]
print(find_smallest(data))

Type this up in find_smallest_number.py. Try to trace through the execution. It’s critical to understand how the variable smallest is changing through the loop, and why the function find_smallest() does in fact identify the smallest value in a list. (Remember: the more negative a number, the smaller. Thus, -10 is less than or “smaller” than -4.)

With that, consider this partially complete program:

def find_smallest(A):
    smallest = A[0]
    for k in A:
        if k < smallest:
            smallest = k
    return smallest

def find_largest(A):
    # Insert your code here:

def find_span(A):
    smallest = find_smallest(A)
    largest = find_largest(A)
    span = largest - smallest
    return span

data = [-2.3, -1.22, 1.6, -0.5, 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]
print('span: ', find_span(data))

Exercise 1.2.24

Exercise 1.2.24

In extremes.py complete the above so that the span of the data is computed and printed. This is the difference between the largest and smallest values.

Next, let’s write some (partially complete) code for summary statistics:

import math

def compute_mean(A):
    # Insert your code here:

def compute_std(A):
    mean = compute_mean(A)
    total = 0
    for k in A:
        total += (k-mean)**2
    std = math.sqrt(total / len(A))
    return std

data = [-2.3, -1.22, 1.6, -10.5, 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]
print('mean =', compute_mean(data))
print('standard deviation =', compute_std(data))

Note: The mean of a list of numbers is the total (add the numbers up) divided by “how many numbers there are” in the list. It is sometimes called the “average” (less precise) or “arithmetic mean” (more precise). We will simply call it the “mean.”

The standard deviation is more involved: Consider data like:

10, 20, 30, 40, 50

This has a mean of 30. Here, a different set of data that also has a mean of 30.

28, 29, 30, 31, 32
  • Clearly, the second set of data is very bunched up around 30 while the first is more spread out.
  • The standard deviation is a measure (a single number) that describes how much the values vary from the mean.
  • The more spread out, the higher the standard deviation.
  • To calculate it, we take the difference of each data point from the mean, square it (to make it always positive), then add all of these up. This gives something called the variance.
  • Because we squared our values, the units of variance often do not make sense. For instance, if we are working with temperature in degrees Fahrenheit, the variance is in units of “degrees Fahrenheit squared.”
    • We take the square root of the variance to obtain the standard deviation, which will have the same units as the original measurements.
  • If you have never seen this before, it’s worth computing by hand with the data above, just so you understand how it looks on paper. Then compare with the results from the program.

Exercise 1.2.25

Exercise 1.2.25

In stats2.py complete the code above. You should get as output, approximately

mean = 0.1627272727272727
standard deviation = 4.955716792313685

Next, let’s tackle the more challenging problem of identifying outlying values (or outliers): Take a closer look at the data:

data = [-2.3, -1.22, 1.6, -10.5 , 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]

We see that two data values seem to be far outside the range of the other data. Suppose we use the following approach to identifying outliers:

  • Compute the mean and standard deviation.
  • Identify those data that lie further than two standard deviations away from the mean.
  • Call these outliers. easy to work with. Let’s write a function to do this:
def find_outliers(A):
    mean = compute_mean(A)
    std = compute_std(A)
    outliers = []
    for k in A:
        if k < (mean - 2*std):
            outliers.append(k)
        elif k > (mean + 2*std):
            outliers.append(k)
    return outliers

Exercise 1.2.26

Exercise 1.2.26

In stats3.py ,add the above to your code from the earlier exercise, and add print statements to the bottom so that the output is:

mean = 0.1627272727272727
standard deviation = 4.955716792313685
Found outlier: -10.5
Found outlier: 11.03

1.2.12    A Function That Calls Itself

Recursion is a somewhat advanced topic (not on any exam).

Consider this example:

def factorial(n):
    # print(n)
    if n == 1:
        return 1
    else:
        m = factorial(n-1)
        return n * m

print(factorial(3))
print(factorial(5))

Exercise 1.2.27

Exercise 1.2.27

Type up the above in factorial.py. Trace through what happens when factorial(5) is called. Remove the # symbol so that the print statement executes.

  • It’s allowable for a function to call itself. Such a function is called a recursive function, and the resulting behavior is called recursion. The above example computes numbers like:       \(1 × 2 × 3 × 4 × 5 × = 120\)
  • (This ascending multiplication is called factorial.)
  • For recursion to work, the successive function calls have to end to prevent an infinite runtime. In the above case, n eventually becomes 1.
  • In this case,there’s no further call to itself. Recursion is hard to understand, and will be featured in later programming courses.
  • Surprisingly, many problems are solvable elegantly and efficiently using recursion.
  • It is possible to do recursion improperly, in which case a program is set up to recurse forever. In this case, Python will give up after too many recursions.

1.2.13   When Things Go Wrong

In each of the exercises below, first try to identify the error just by reading. Then type up the program to confirm, and after that, fix the error.

Exercise 1.2.28

Exercise 1.2.28
def square_it(x):
    y = x * x

a = 5
b = square_it(a)
print(b)

Identify and fix the error in error1.py. The function should return the square of its input.

Exercise 1.2.29

Exercise 1.2.29
def larger(x,y)
    if x > y:
        return x
    else:
        return y

print(bigger(4,5))

Identify and fix the error in error2.py. The function should return whichever of x or y is larger.

Exercise 1.2.30

Exercise 1.2.30
def find_smallest(A):
    smallest = 1000
    for k in A:
        if k < smallest:
            smallest = k
    return smallest

B = [2, 1, 4, 3]
print(find_smallest(B))

Does the above program work? What is the reasoning behind the line smallest = 1000? What would go wrong if you removed it altogether? Can you create a list (change the data in B) which will cause the function to fail to find the smallest number. Fix the issue in error3.py.

End-Of-Module Problems

Full credit is 100 pts. There is no extra credit.

Problem 1.2.1 (60 pts)

Problem 1.2.1 (60 pts)

Write a function smallest_of_two that takes as arguments two lists of numbers (ints or floats) and returns a single float equal to the smallest value in either list. Input lists can be assumed to always contain only numbers.

Examples of output:

smallest_of_two([1, 3, 4], [0, 5, 6]) would return 0.0. smallest_of_two([1, -1, 1], [0, 0, -2.5]) would return -2.5.

Submit as smallest_of_two.py.

Problem 1.2.2 (60 pts)

Problem 1.2.2 (60 pts)

Write a function greatest_sum that takes as arguments two lists of numbers (ints or floats) and for whichever list’s numbers’ sum is greater, the function returns that list.

As an example, given:

A = [5, 4, 1]
B = [3, 2, 7]

A’s sum is \(5 + 4 + 1 = 10\) and B’s sum is \(3 + 2 + 7 = 12\), so greatest_sum(A, B) would return [3, 2, 7]. If the sums are the same, the function can return either list.

Submit as greatest_sum.py.

Problem 1.2.3 (60 pts)

Problem 1.2.3 (60 pts)

Write a function reverse_factorial(x) that determines if its parameter x (a positive integer) is a factorial or not. If it is, return the number whose factorial is x, if not, return -1. x can take on any positive integer value.

def reverse_factorial(x):
    # your code here

print(reverse_factorial(24)) # yields 4
print(reverse_factorial(25)) # yields -1
print(reverse_factorial(120)) # yields 5
print(reverse_factorial(304888344611713860501504000000)) # yields 28

Submit as reverse_factorial.py.