Week 5: Fruitful Functions
Class Recordings
To access recordings, sign in with your @swarthmore.edu account.
Monday |
||
Wednesday |
||
Friday |
Announcements
-
Lab 4 is available now. NOTE: The written portion is due at the start of class on Friday!
Week 5 Topics
-
Stack diagram practice
-
Functions with return
-
The list data type
-
list operations
-
-
Functions with lists
Monday
Stack Example 2
def addNumbers(num1,num2):
total = num1 + num2
print("In addNumbers:")
print(num1)
print(num2)
print(total)
#Show the stack diagram as it exists at this point
return total
def main():
x = 5
y = 9
ans = addNumbers(x,y)
print("In main:")
print(ans)
print(total) #Gives an error. Why?
main()
Answer the following questions:
-
What is the scope, respectively, of
x
,y
,ans
,num1
,num2
, andtotal
? -
Why does the last line of
main()
give a runtime error?
Stack Example 3
Try another example of stack drawing.
def absval(x,y):
if x > y:
aval = x-y
else:
aval = y-x
# draw stack to this point!
# as it would look just before return...
return aval
def main():
a = 10
b = 35
result = absval(a,b)
print("abs(%d-%d) = %d" % (a,b,result))
main()
Exercise: total
In totally.py
, define a function total(n)
, which takes a positive
integer as input and returns the sum of the first n
positive
integers. Then, call your function on several different inputs.
Strings, Slicing, and Mutability (oh my!)
We know of several string features so far. Assuming s
is a string variable:
-
len(s)
to get the length of the string. -
s + "more characters"
to concatenate strings. -
s * 3
to repeat a string multiple times. -
Strings can be compared with relational operators (e.g.,
<
,>
,==
). -
s[i]
to index into the string and access the ith character.
String Slicing
Python supports a more powerful indexing (s[]
) mechanism called slicing.
Slicing is a very useful shorthand notation for grabbing characters from a
string or items from a list. The syntax is similar to range()
where you can
have a start and a stop. Here are some examples:
>>> s = "ABCDEFGHIJKLMNOP" >>> s[2:5] 'CDE' >>> s[:6] 'ABCDEF' >>> s[6:] 'GHIJKLMNOP'
The first example above (S[2:5]
) grabs characters from positions 2, 3, and 4
(i.e., the start is 2, the stop is 5. Like range()
string slicing goes up to
but does not include the stop value.
The next two examples use implied defaults:
-
S[:6]
is the same asS[0:6
] -
S[6:]
is the same asS[6:len(s)]
(grab from position 6 to the end).
Slicing exercise
Write a first_half()
function in slicing.py
that takes a string as input
and returns the first half of the string. For odd-length strings, it’s ok to
round down when determining "half".
You should test your function by writing a main()
function that prompts the
user for a string, passes that string to first_half()
, saves the function’s
return value into a variable, and then prints the result.
Advanced bonus exercise
Given a string representation of a strand of RNA, add a function to
slicing.py
that prints 3-character groups from the strand. For example, given
this:
strand = "ACUAUGACAUGCCAACGCUAGCGUCCU" ---
Can you print out this (i.e., the first three characters, then the next three, etc)?
0 ACU 3 AUG 6 ACA 9 UGC 12 CAA 15 CGC 18 UAG 21 CGU 24 CCU ---
Hint: the range()
function can take three arguments: a start, a stop, and
a step. The step determines how quickly range()
counts up. For example:
# Counts from 0 to 25 by 5. >>> for i in range(0, 26, 5): ... print(i) ... 0 5 10 15 20 25
String mutability
While strings support indexing with brackets ([]
), Python does not allow you
to change the value in a position — the indices are read-only. For example:
>>> name = "Kevin" # Normal indexing works fine for reading characters. >>> name[1] 'e' # Can't modify characters. >>> name[1] = "a" Traceback (most recent call last): File "", line 1, in TypeError: 'str' object does not support item assignment
Because strings allow reading but not writing, they’re said to be immutable. That is, their contents cannot be mutated (changed). Coming up soon, we’ll start to see data types that are mutable!
Wednesday
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 -
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.
Friday
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 |
---|---|
|
Randomly choose and return one element from a sequence (e.g., one item from a list). |
|
Chose and return a random number from [start, stop-1]. Like |
|
Shuffle the contents of a list. |
|
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 theexit()
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, callget_choice
to get the user’s input and then callsimulate_game
to determine if the user won, lost, or tied. Print the result and also append it to aresults
list, which you can print at the end of the five rounds to summarize the session.