Week 5: more functions

Monday

allcaps(text) example

Here’s one way to write the allcaps(text) function we were looking at last time:

def allcaps(text):
    """return True if given text is all capital letters"""
    caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    for i in range(len(text)):
        if text[i] not in caps:
            return False   # ends function if we get here
                           # no point in checking further

    # after for loop, if we get here, must be all caps
    return True

As soon as we find a letter that isn’t a capital letter, the function ends (the for loop, too) with the return False line.

If the function gets through the entire for loop, and never does the return False line, then the given text must be all capital letters, so we return True.

isVowel(letter) example

Write a function that, given a single letter, returns True if the letter is a vowel, and False if not.

def isVowel(letter):
    """return True if given letter is a vowel"""
    vowels = list("aeiouAEIOU")
    if letter in vowels:
        return True
    else:
        return False

sumList(L) example

Write a function that, given a python list of numbers, sums the numbers in the list and returns the sum of the list.

def sumList(L):
    """sum the items in L and return the total"""
    total = 0
    for i in range(len(L)):
        total = total + L[i]
    return total

Wednesday

mutable objects in functions

Let’s do another stack-drawing worksheet!

#
# draw the stack and show the output for this program
#

def main():
    data = [20, 88, -13, 200, 301, 199]
    low = 0
    high = 255
    print("data before function call: %s" % (data))
    howmany = limit(data, low, high)
    print("data after function call: %s" % (data))
    print("how many changed: %d" % (howmany))

def limit(L, min, max):
    """check if all items in list are between min and max"""

    count = 0
    for i in range(len(L)):
        if L[i] < min:
            L[i] = min
            count = count + 1
        elif L[i] > max:
            L[i] = max
            count = count + 1

    # draw stack to this point, just before the return
    return count

main()

The limit() function is just going through the list of integers, checking each one if they are less than the minimum (0) or greater than the maximum (255). And if they are, we assign the min or the max to that location in the list (and also add 1 to the count variable).

Here’s what the stack will look like, just before the return:

listfunction

In the above drawing we have a stack frame for main(), and on top of that a stack frame for limit(). In the stack frame for limit() I am ignoring the loop variable, i. Also, since the for loop has already finished, the count variable has been reassigned twice: first to 1, then again to 2.

Here’s the main point of this stack diagram: when the limit() function ends and we return to main(), what will the print(data) line in main() display? Will it display the original list ([20,88,-13,200,301,199]) or the updated list ([20,88,0,200,255,199])???

If you look at the stack diagram, you should see that data points to the modified list, so when we print data from main(), we’ll see the modified list.

Because python lists are mutable, we can change items in a list, in a function, and see those changes even after the function is finished.

Notice that the limit() function doesn’t have to return L, yet we can still see the effects of changing L back in main(). This is often called a side effect, when a function changes data, and the effects are seen back in main().

tic-tac-toe

Let’s look at an example of the above, in a real program. Suppose we want to create a terminal-based tic-tac-toe game:

$ python3 ttt.py
 | |    0 1 2
-----
 | |    3 4 5
-----
 | |    6 7 8
O's turn: 4
 | |    0 1 2
-----
 |O|    3 4 5
-----
 | |    6 7 8
X's turn: 2
 | |X   0 1 2
-----
 |O|    3 4 5
-----
 | |    6 7 8
O's turn: 8
 | |X   0 1 2
-----
 |O|    3 4 5
-----
 | |O   6 7 8
X's turn:

Here’s some starter code that creates the board (a python list) and displays the board as a tic-tac-toe board (3x3 rowsxcolumns):

"""
terminal-based tic-tac-toe game

Spring 2020
"""

from random import *

def main():
    board = list("         ")   # 9 spaces, initial empty board
    display(board)
    player = choice("XO")

def display(board):
    """display 9-item python list as tic-tac-toe board"""
    for i in [0,3,6]:
        print("%s|%s|%s   %s %s %s" % (board[i],board[i+1],board[i+2],i,i+1,i+2))
        if i != 6:
            print("-"*5)
    print()

main()

So the board variable is just a python list of 9 single-space characters:

[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']

And the player variable is either an "X" or an "O" character to start. Your job is to write a turn(board,player) function that, given the board and the player character, asks the user which spot to mark (0-8), and then changes the board accordingly (i.e., either puts an "X" or an "O" in the list).

Here’s what our main() function should look like after you write the turn(board,player) function:

def main():
    board = list("         ")
    display(board)
    player = choice("XO")
    turn(board,player)
    display(board)

Once you have that working, let’s add a while loop to keep playing, taking more turns:

def main():
    board = list("         ")
    display(board)
    player = choice("XO")
    done = False
    while not done:
        turn(board,player)
        display(board)
        # change whose turn it is

This loop uses the while not done pattern, where done is a simple boolean variable. I find this useful when the condition to end the loop is complex, and may depend on more than thing. For example, the game might be over, or a draw, or the user just wants to quit early. What’s not shown yet, is some branching options where done gets set to True, like these pseudo-code examples:

if the game is over:
   done = True
elif the user wants to quit:
   done = True
else:
   keep going with the game...

Note: with the above main(), and no way to really end the game, you may have to use Ctrl-c to kill the program.

Friday

  • Quiz 2

tic-tac-toe (again)

Here’s one way to write the while loop for the tic-tac-toe turn(..) function:

def turn(board,player):
    """get move for player, change the board"""
    while True:
        index = int(input("%s's turn: " % (player)))
        if board[index] == " ":
            board[index] = player
            return
        else:
            print("That space is already taken...")

This just checks to make sure the space chosen isn’t already taken. Other checks we still need to add: make sure the enter an integer from 0-8, and make sure they don’t enter something invalid (like 'zebra').

The while True syntax is just an easy way to get the loop going, without having to worry about the condition. We want to keep looping if they enter a space already take, or they enter a non-integer, or they enter an integer that’s out of range. That’s a complicated condition to write and get correct. Sometimes it is easier to just get the loop going, then have some if\elif\else branches that check for each separate condition.

Here’s the same function, but with an additional check that the number entered is in range (still won’t work for input 'zebra'):

def turn(board,player):
    """get move for player, change the board"""
    while True:
        index = int(input("%s's turn: " % (player)))
        if index < 0 or index > 8:
            print("please enter a number from 0-8...")
        elif board[index] == " ":
            board[index] = player
            return
        else:
            print("that spot is already taken")

In both of these examples the return line is the only way out of the loop. Once the return statement is hit, the loop and the function are done, and the program returns to main() to continue with the rest of the program.

Note: we are not actually returning anything, we’re just saying, "if you get here, we’re done, so stop this function and go back to main and carry on from there".

objects and methods

Both python strings and python lists are examples of objects. An object is just some data plus some methods (similar to functions) wrapped up together into one thing. We’ll learn more about objects as we go. For now we just want to learn the syntax of using them: object.method()

example

>>> course = "Introduction to Computer Science"
>>> course.count("o")
4
>>> course.count("z")
0
>>> course.isalpha()
False
>>> course.upper()
'INTRODUCTION TO COMPUTER SCIENCE'
>>> grades = [88,90,78,99,93]
>>> grades.sort()
>>> print(grades)
[78, 88, 90, 93, 99]
>>> grades.append(75)
>>> print(grades)
[78, 88, 90, 93, 99, 75]

explanation/notes

The string methods used above are:

  • count() — returns the number of occurrences of the given substring

  • isalpha() — returns True if all characters in the string are letters

  • upper() — returns an uppercase version of the string

There are many other str methods. In string objects, the data are the characters in the string.

The list methods used above are:

  • sort() — sorts the list in place

  • append() — adds the given argument to the end of the list

In list objects, the data are the items in the list.

object.method() syntax

Methods are always called with the dot syntax: object.method()

Again, methods are similar to functions, except they are tied to the type of object, and are called with this special syntax.

common string methods

Strings are objects in Python, and thus have methods that we can invoke on them. There are a lot of methods in the str library for creating new string objects from existing string objects and for testing properties of strings. Keep in mind that strings are immutable!

Here are a few str methods that may be particularly useful (run help(str) in the python interpreter to see the full set):

str method result

upper()

return copy of str converted to uppercase

lower()

return copy of str converted to lowercase

isalpha()

return True if string is all alphabetic characters

isdigit()

return True if string is all digits

count(sub)

return number of occurrences of sub

index(sub)

return index of first occurrence of sub

strip()

strip off leading and trailing whitespace

split()

split into list of "words" (see below)

>>> S = "we LOVE cs"
>>> S.upper()
'WE LOVE CS'
>>> S.lower()
'we love cs'
>>> S.isalpha()
False
>>> S.isdigit()
False
>>> S.count(" ")
2
>>> S.index("L")
3
>>> S.split()
['we', 'LOVE', 'cs']
>>> S = "   we love cs    "
>>> len(S)
17
>>> S = S.strip()
>>> len(S)
10
>>> print(S)
we love cs

common list methods

Lists are also objects in Python, and thus have methods that we can invoke on them. Here are a few that may be particularly useful (run help(list) in the python interpreter to see the full set):

list method result

append(item)

add item to end of list

insert(index,item)

insert item at index

extend(L1)

add list L1 to original list

sort()

sort the list

reverse()

reverse the list

count(item)

return number of occurrences of item in list

index(item)

return index of first occurrence of item

pop(index)

remove and return item at index

>>> L = list("ABCDEFG")
>>> print(L)
['A', 'B', 'C', 'D', 'E', 'F', 'G']
>>> L.append("X")
>>> print(L)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'X']
>>> L.extend(["Y","Z"])
>>> print(L)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'X', 'Y', 'Z']
>>> L.reverse()
>>> print(L)
['Z', 'Y', 'X', 'G', 'F', 'E', 'D', 'C', 'B', 'A']
>>> L.sort()
>>> print(L)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'X', 'Y', 'Z']
>>> L.count("E")
1
>>> L.index("E")
4
>>> L.pop(4)
'E'
>>> print(L)
['A', 'B', 'C', 'D', 'F', 'G', 'X', 'Y', 'Z']
>>> L.insert(1,"hello")
>>> print(L)
['A', 'hello', 'B', 'C', 'D', 'F', 'G', 'X', 'Y', 'Z']

To see the full documentation for both the str and list classes:

$ python3
>>> help(str)
>>> help(list)