Lab 2: Functions and Recursion

Functions

2.0 Why Write Functions?

The primary job of a programmer is to divide large tasks into smaller ones. Ultimately, we want to divide tasks into pieces small enough for the computer to handle them.

I want you to think in terms of units of function. Giving them names allows us to organize our thinking, and also allows us to reuse functions. If we discover an error in one of our functions, we can often isolate the error to within that function, making it much easier to find (and fix!) bugs.

Group Task:

  • Starting with the task "Drive from Madison to Milwaukee", take turns subdividing a task into its component tasks.

2.1 Function Syntax in Python

def addSeven(x):
  """This one-line function takes a single number as input and returns that number plus 7."""
  return x+7

The keyword def indicates the start of a function definition. The first line of the function consists of that keyword, followed by the name of the function, followed by parentheses containing any parameters to the function, and completed with a colon. Notice that python does not ask you to indicate the type expected by your parameters.

The line bracketed by triple quotation marks is the function's help text. Try running help(raw_input) in the console. The text that you see is the text on the second line of the raw_input function. The help text is optional, but this is a good place to write down the purpose of the function and any details about how it should be used.

Each following line is part of the function's body. Lines of code within a function definition behave just like lines of code outside, but they are run only when the function is called.

2.2 Print versus Return

The print and return statements are probably the two most useful statements in your coding toolbox. They are also unfortunately easy to confuse.

The return statement occurs only within a function. It signals that the function has completed its task and the program immediately returns (thus the statement's name) to the line where the function was called. If you place an expression with the return statement, then the program still returns to the line where the function was called, and it also replaces the call to the function with the value of that expression. So, for example, addSeven(addSeven(1)) will evaluate to 15. If we want to store the function's work for later use, we can set a variable equal to its output. Note that the variable stores the product of the function's evaluation, not the function itself, just like a variable stores the value of an expression and not the actual expression.

x = 3 + addSeven(5)
print x #This should print 15

The print statement sends the expression that follows it to the screen. At this point, the program never sees any of the data in that expression again. The program then executes the line immediately after the print statement.

You can think of print as a statement for talking to the user, while return talks to the rest of the program.

Not all functions need an explicit return statement. Some may have several. If a running program reaches the end of a function without finding some return statement along the way, there is an implied return line with no attached expression at the end.

Task:

  • Write a function that gathers 3 inputs from the user, the sides of a triangle. You can assume that it will always be given numbers as inputs, but should handle the possibility that those numbers might not form a triangle. Your function should print a statement indicating whether the inputs formed a triangle. It should return the area of the triangle. (Design question: What should your function return if given bad inputs?)

2.3 Calling Functions

When I want to use a function, I "call" it using its name followed by parentheses containing any inputs I want to give to the function. Ideally, the number of inputs I give matches the number of parameters the function takes. You can think of "inputs" as what the caller says and "parameters" as what the function expects to hear.

Tasks:

  • The function abs() calculates a number's absolute value. Try out the default version, then write your own.
  • Write a small calculator program that takes two or three inputs. The first two inputs should be numbers, and the third should indicate which operation the function should perform on the two numbers. If no third input is given, your function should display all of the operations and the answers they generate. (Hint: The raw_input() function has an optional parameter. Try using help() to see how that optional input is handled.)

2.4 The Lifespan of a Variable

Note: I mentioned variable lifespan last week in the context of if statements. I did some research of my own and determined that python actually ignores if statements when it comes to scope. It only cares about functions. Sorry for the confusion!

def happyBirthday(name):
  line = "Happy birthday to you!"
  print line
  print line
  print "Happy birthday, dear", name
  print line

Each function has its own namespace, a set of variables that call this function's body "home". When a run through the function completes, its namespace and all variables in it are destroyed. For the happyBirthday() function above, its namespace contains two variables. name, a parameter, is predefined with some value by the call to the function. line, on the other hand, exists immediately but does not have a value attached to it until it is given one by code in the function body. Local variables like line become usable when they are first defined. Both types cease to be usable when the function returns. If I were to try to print line or print name outside of the body of this function, I would get an error.

Variables cannot be used outside of the function in which they were created, but we have created variables that aren't in a function at all. What happens with those? You can use their values within a function, but if you try to change them you will instead create a new variable with the same name.

x = "Outside"
def myFun():
  #print x
  x = "Inside"
  print x
myFun()
print x

Try the code above. You will note that I have commented out one line of myFun(). See what happens if you uncomment that line. Can you explain why you get that error message?

Task:

  • Talk to your neighbor. Explain in your own words why each print statement displays what it does.
  • When you are both ready, try to predict the behavior of the following programs. Were your predictions correct?

def getInput():
  input = raw_input()
  if input == "True":
    x = "middle"
  def handleInput():
    print x
  handleInput()

x = "outer"
getInput()


def xIsAString():
  x = "Lines"
  print x
def xIsAnInt():
  x = 12
  print x

xIsAnInt()
xIsAString()

2.5 Lists and Other "Mutables"

When I try to change a variable whose home is not the closest function (that is, when I try to change a variable that isn't local), I instead create a new variable with the same name, hiding the remote variable. If I change part of a list, however, that change is made to the non-local variable and the change persists after my function completes. This is true even if I hand a list to a function as an input. Python behaves this way because copying lists and other larger data structures can be very, very expensive. If you want to make changes to a list locally without changing anything outside your function, use the list() function to construct a new list out of the contents of the old one. For example: newList = list(oldList). Changes to newList will not affect oldList. If you want to make a local copy of some other data structure, you can import the copy module, which has some more general functions.

Recursion

For the rest of this lab, we will work with one of the fundamental tools for writing programs that deal with large amounts of data or large amounts of processing. There are two basic approaches to dealing with problems of unknown size, recursion and iteration. We will talk about the latter next week. Most programming languages support one of the two options well, and the other poorly. Python is unusual in that it is very good at both types of scaling.

2.6 More on Lists

Tools for solving large problems are quite difficult to test without inputs that can also scale up to arbitrarily large sizes. Fortunately, we can use lists. Recall from last week that we can create lists using square brackets around some comma-separated "elements". [4,"element",5.3,[2,3]] Notice that list elements can also be other lists.

If I want to access the value of the element at a particular index in the list, I can use myList[INDEX], replacing INDEX with the number of the position in the list that I am interested in. Recall that L[0] is the first value in a list. If I want the last element in a list, I can put -1 into the square brackets. -2 will give me the second-to-last element, and so on.

Frequently, you will want more than just one element. For one of the problems in the next section, you will want to be able to store almost the entire list, but with its first element removed. To do this, we place a colon inside the square brackets: L[:] gives us the entirety of L. L[a:b] gives the sublist that starts with index a and ends just before index b.

Finally, here are two useful list-related functions: len() and range(). The former, when given a list as input, reports how many elements are in the list. The latter creates and returns a list of numbers. If you give it one input n, the list is of the form [0, 1, 2, ... n-1]. If given two inputs a and b, the list looks like [a, a+1, ... b-2, b-1].

2.7 Functions Can Call Themselves

def callMyself():
  callMyself()

What happens when we call the function above? Try it.

If you have a function call itself without changing the inputs in some way, you will find yourself in an infinite loop. There are times when you might want to have your program do something similar to this, but usually you will want to do direct recursion, where you have a function call itself, but with smaller or simpler inputs.

Consider the function factorial() in the math module. The factorial of an integer is equal to the product of all integers from 1 up to that number. So math.factorial(4) should evaluate to 1*2*3*4 = 24. To implement this function, we need a way to have one function call cascade into the next (to build up the answer we're looking for) and we need a way to stop the cascade when we find that answer.

def factorial(n):
  if n==1:
    return 1
  return n*factorial(n-1)

Notice that each call uses a smaller input than before, until we finally call factorial(1) and the chain completes. This is the pattern of recursion.

Tasks:

  • Write a "fizz buzz" program. Given a number, it should print from 1 up to that number, but any number divisible by 3 should be skipped and "fizz" printed instead. Any number divisible by 7 should be skipped and "buzz" printed instead.
  • Consider the function sum(). It takes a list (of numbers) as its input and returns the sum of the numbers in that list. Write a function product() that returns the product of the numbers in a list.
  • Write a short program that asks the user to input a "password". The program should continue to ask for guesses and give the user hints until the user finally enters the correct password.

2.8 Functions Can Call Each-Other

Direct recursion is one of the simplest ways to create programs that can repeat an arbitrary number of times. Another very useful pattern is to have a pair of functions that call each-other.

Task:

  • Write a function that, given a list of numbers, reports whether the odd-indexed numbers have a greater sum than the even-indexed numbers. (Hint: You will probably want to write several functions. My solution used 3.)

2.9 Using Recursion for Complex Data

You may have noticed that python will allow you to print lists. (If you haven't, try that now.) What happens if you print a list whose elements are themselves lists? It works, but it is not very easy to read. Let's write a program to display these sorts of nested lists in a cleaner format. Our first design question is what format to use. There are a number of reasonable options, but probably the most common one happens to be the same way python structures code! If I am given the following list of numbers:
[3, [4, 2], 7, [[1], 1, 1], 2], I print out the following:

List:
| 3
| List:
| | 4
| | 2
| 7
| List:
| | List:
| | | 1
| | 1
| | 1
| 2

Tasks:

  • Write a printList() function which, given a list, produces output similar to the example above.
  • Think about how you might use your printList() function to simplify the game you made at the end of last week. What other components might you need before you could make use of this function?