Week 5: Fruitful Functions

Writing functions

Recall the general template for writing a function:

def NAME(PARAMETER1, PARAMETER2, ...):
    """
    Purpose
    Parameter description(s)
    Return value description (if any)
    """

    # DO STUFF

    # Optional return statement to produce function output
    return VALUE

Modifying Parameters

What is the result of modifying the parameter variable for a function? Do the results effect the arguments in the original calling function, or are all changes ignored? We’ll explore this question in more depth this week.

Take a look at oops_squareFunc.py, which squares the parameter values in a function. What do you think will happen at the end of this program? Test it out and then we will trace through the program to see what happened.

Immutable data types (float,str,int,bool) can never be modified; thus, any changes to the variable require reassigning it a new value. Reassignment breaks any sharing of values between the parameter and its original argument.

Mutable data types are different - they can be modified without reassignment. As an example, we will return to the list datatype and see its methods.

Lists

Lists are a data type in Python that hold a collection of items. Lists are a type of sequence of items. As we will see, many of the operations we use on strings can also be used on lists, since both are sequences. There are two major differences between lists and strings: lists are a sequence of items where the items can be any of any type including (e.g., str, int, etc) while strings are strictly a sequence of characters. The second meaningful difference is that lists are mutable, meaning we can change the contents of the list. Strings are immutable.

List operations

We can do similar things to lists as with strings:

  • +: concatenation

  • *: repeated concatenation (similar to multiplying a string with an integer)

  • len(lst): number of items in list

  • lst[i]: index into list to get ith item

  • lst[x:y]: slicing to retrieve items at indices [x,y) of the list

An operation that is unique to lists is append(), which adds an item to the list (and thus changes its content):

lst = []            # empty list
lst.append(5)       # add 5 to lst

lst2 = [0, 10, 20]
lst2.append(30)     # adds 30 to lst2

We can also iterate over lists just as we did with strings:

for i in range(len(lst)):    # we can iterate over a list
    print(lst[i])

List exercise

Fill in the missing code in squareList-return.py to complete a program that squares the items of a list. In that file, main() prompts the user for input three times and builds a list out of the input values. Pass that list to squareList(), which should iterate over the list and produce a new list containing the squares of the original values.

Breaking out of loops

Next, we’ll take a brief detour to show you the break statement, which you might find helpful for current or future lab assignments.

If you’re executing a loop, the break statement will immediately end the loop.

For example, consider this for loop:

for i in range(10):
  print(i)

  if i == 4:
    break

Normally, this for loop would execute ten times (i from 0 to 9), but by adding a conditional check that executes break when i == 4, the loop terminates early.

We can do the same thing with while loops:

i = 0
while i < 10:
  print(i)
  i = i + 1

  if i == 4:
    break

Note that you should use break carefully. The examples above are just meant to show how break works. If you really wanted a loop to execute five times, it would make more sense to just use for i in range(5) from the beginning.

Using break does enable a potentially interesting pattern in which a loop will continue indefinitely until a desired condition:

while True:
  # Do work

  if INTERESTING_CONDITION:
    break

Of course, the INTERESTING_CONDITION will depend on the problem at hand. Using break isn’t required to solve labs, but it’s occasionally helpful for structuring your loop logic.

List Mutability

Another thing we can do with lists that we can’t do with strings is change a single element:

text = "hello"
text[0] = "j"     # illegal: Strings are immutable.

lst = [0, 10, 20]
lst[0] = 5        # legal: Changes first element of list.
print(lst)

Side effects with mutable parameters

Previously, we showed that reassigning the parameter variable had no effect on the original argument that was sent to the function. This is always the case with immutable data types as once they are created, they cannot be modified.

However, there are circumstances where parameters of mutable data type (e.g., list) can be modified in a function, and the result will have the side effect of also changing the argument variable, despite not returning anything. This can be a desired outcome, or it could be the source of an error in what we want our program to do. Using stack traces to understand this phenomena will help us understand why.

in all circumstances, use of the assignment operator (=) resets any connections variables may share (the arrow gets changed to a new location); this applies to lists as well.

Exercise: list modification with side effects

In squareList-inplace.py, we will trace through the program and show that, despite not having a return value, our arguments are forever impacted by the call to the function.

Pseudorandom Numbers

Python has a random library that you can import into your programs and use to generate random numbers or choices. The actual numbers are pseudorandom, meaning they are not truly random. For CS 21 purposes though, they’re random enough.

random Library Synax

First import the random library:

from random import *

Then use one of the various functions in the library. The most useful functions are likely to be:

Function Description

choice(seq)

Randomly choose and return one element from a sequence (e.g., one item from a list).

randrange(start, stop)

Chose and return a random number from [start, stop-1]. Like range(), the start is optional, and Python assumes 0 if you omit it.

shuffle(lst)

Shuffle the contents of a list.

random()

Returns a random float from [0,1).

random Examples

To simulate flipping a coin, you could use any of these:

flip = choice(["heads","tails"])
flip = choice("HT")
flip = randrange(2)                 # assume 0 is heads, 1 is tails
flip = random()                     # assume < 0.5 is heads

To simulate rolling a 6-sided die:

result = randrange(1,7)

To help decide what to order for dinner:

takeout = ["Pizza", "Wings", "Curry", "Sandwich", "Soup"]
dinner = choice(takeout)

Exercise

Write a program, rps.py, to play rock-paper-scissors against a randomly-generated opponent’s choice. At the end, print a history of victories, defeats, and ties.

I would suggest dividing up the responsibilities of the game into three functions:

  • get_choice: Prompt the user for a choice and validate their input. Valid choices are "R" for rock, "P" for paper, "S" for scissors, and "QUIT" to exit the program. For any other input, you should prompt again. If the user types "QUIT", you can call the exit() function to stop the program.

  • simulate_game: Take the user’s choice as input, choose a random action for the opponent, and then compare to see who wins. Return one of "WIN", "LOSE", or "TIE".

  • main: Loop up to five times to simulate five rounds. In each round, call get_choice to get the user’s input and then call simulate_game to determine if the user won, lost, or tied. Print the result and also append it to a results list, which you can print at the end of the five rounds to summarize the session.