1. Review: Early Return

Which of the following functions works for determining if val is an element in aList?

In [1]:
def isElementOf1(val, aList):
    """Version ONE for a list membership function.
    """
    for elt in aList:
        if elt==val:
            return True
        else:
            return False
        
def isElementOf2(val, aList):
    """Version TWO for a list membership function.
    """
    for elt in aList:
        if elt==val:
            return True
        return False
        
def isElementOf3(val, aList):
    """Version THREE for a list membership function.
    """
    for elt in aList:
        if elt==val:
            return True
    return False

Let's test the behavior of each function with the given list:

In [2]:
animals = ["cat", "mouse", "dog", "rabbit"]

# Predict which of these will work before executing this cell
print("isElementOf1('mouse', animals) =>", isElementOf1('mouse', animals))
print("isElementOf2('mouse', animals) =>", isElementOf2('mouse', animals))
print("isElementOf3('mouse', animals) =>", isElementOf3('mouse', animals))
isElementOf1('mouse', animals) => False
isElementOf2('mouse', animals) => False
isElementOf3('mouse', animals) => True

2. List membership

Here are some lists from the lecture slides that we have seen before:

In [3]:
primes = [2, 3, 5, 7, 11, 13, 17, 19] # List of primes less than 20
bools   = [1<2, 1==2, 1>2] 
houses  = ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin']
strings = ['ab' + 'cd', 'ma'*4] 
people = ['Hermione Granger', 'Harry Potter', 
          'Ron Weasley', 'Luna Lovegood']

# A list of string lists
animalLists = [['duck', 'raccoon'],
               ['fox', 'raven', 'gosling'], [], ['turkey']]

# A heterogeneous list (values of different types)
stuff = [17, True, 'foo', None, [42, False, 'bar']]
empty = [] # An empty list

The built-in operators in and not in

We will use Python's built-in in and not in operators rather than isElementOf.
The in operator simplifies functions such as isVowel and isValidGesture below:

In [4]:
def isVowel(char):
    return len(char) == 1 and char.lower() in 'aeiou' # an example of using 'in' with a string

def isValidGesture(g):
    return g in ['rock', 'paper', 'scissors'] # an example of using 'in' with a list

for c in 'ABcde':
    print(c, ':', isVowel(c))

print()
    
for g in ['rock', 'paper', 'scissors', 'Spock', 'paepr', 'sissors']:
    print(g, ':', isValidGesture(g))
A : True
B : False
c : False
d : False
e : True

rock : True
paper : True
scissors : True
Spock : False
paepr : False
sissors : False

YOUR TURN: Using in

Use in with lists and to check for substrings in strings.

Predict what each of the calls to testIn will do.

In [5]:
def testIn(thing, collection):
    print(thing, 'in', collection, '=>', thing in collection)
In [6]:
testIn('Ravenclaw', houses)
Ravenclaw in ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'] => True
In [7]:
testIn('Munger', houses)
Munger in ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'] => False
In [8]:
testIn('Hermione Granger', people)
Hermione Granger in ['Hermione Granger', 'Harry Potter', 'Ron Weasley', 'Luna Lovegood'] => True
In [9]:
testIn('Hermione', people)
Hermione in ['Hermione Granger', 'Harry Potter', 'Ron Weasley', 'Luna Lovegood'] => False
In [10]:
testIn('m', 'Hermione Granger')
m in Hermione Granger => True
In [11]:
testIn('x', 'Hermione Granger')
x in Hermione Granger => False
In [12]:
testIn('anger', 'Hermione Granger')
anger in Hermione Granger => True
In [13]:
testIn('oneG', 'Hermione Granger')
oneG in Hermione Granger => False
In [14]:
testIn('one G', 'Hermione Granger')
one G in Hermione Granger => True
In [15]:
testIn([2, 3], [1, 2, 3, 4])
[2, 3] in [1, 2, 3, 4] => False

Look at this concise way of testing for multiple substrings at once

In [16]:
for string in ['e', 'x', 'Hermione', 'oneG', 'one G']:
    print(string, ':', string in 'Hermione Granger')
e : True
x : False
Hermione : True
oneG : False
one G : True

3. Loops Accumulating a List Result

In previous lectures we have seen that it is common to use loops in conjunction with accumulating variables that accumulate results from processing elements within the loop.

Below are examples of functions with while and for loops that accumulate a sum in a number variable.

In [17]:
def sumHalves(n):
    '''Sum the positive successive halves of n'''
    sumSoFar = 0         # initialize accumulator variable
    while (n > 0):
        sumSoFar += n
        n = n//2 
    return sumSoFar 

sumHalves(22)
Out[17]:
41
In [18]:
def sumList(nums):
    sumSoFar = 0         # initialize accumulator variable
    for n in nums:
        sumSoFar += n 
    return sumSoFar

sumList([8, 3, 10, 4, 5])
Out[18]:
30

In this lecture, we focus on loops in which the value that's accumulated is a list.

3.1 Accumulating a List in a Loop

We can create new lists through accumulation, for example, a list of a number and its halves (see slide 6).

In [19]:
def halves(n):
    """Returns a list of successive halves created from n."""
    result = []           # 1. initialize accumulator for list
    while (n > 0):
        result.append(n)  # 2. update list accumulator by adding item to end
        n = n//2          # 3. make n smaller  
    return result         # 4. return accumulator

halves(22)
Out[19]:
[22, 11, 5, 2, 1]

Alternatively, we can use list concatentation to add an item to the end of a list:

In [20]:
def halvesAlt(n):
    """Returns a list of successive halves created from n."""
    result = []           # 1. initialize accumulator for list
    while (n > 0):
        result += [n]     # 2. update list accumulator by adding item to end
        n = n//2          # 3. make n smaller  
    return result         # 4. return accumulator

halves(22)
Out[20]:
[22, 11, 5, 2, 1]

3.2 Double Accumulation

We can have more than one accumulation happening at the same time, as shown in the function below, which accumulate into a list the intermediate results accumulated in the numeric sumSoFar variable:

In [21]:
def partialSums(nums):
    """Returns a list of partial sums from a given list.
    """
    # initialize accumulators
    sumSoFar = 0
    partials = []
    
    # continously update the accumulators
    for n in nums:
        sumSoFar += n
        partials.append(sumSoFar)
        
    return partials

partialSums([8, 3, 10, 4, 5])
Out[21]:
[8, 11, 21, 25, 30]

3.3. Exercise 1: prefixes

Write a function named prefixes() that, given a string, returns a list of nonempty prefixes of the string,
ordered from shortest to longest.

prefixes('Python') --> ['P','Py','Pyt','Pyth’,'Pytho’,'Python']

In [22]:
def prefixes(phrase):
    """Given a string, returns a list of nonempty prefixes of the string, 
    ordered from shortest to longest.
    """
    # Flesh out this function body  
    # Your code here
    prefixList = []            # accumulator to hold final result as a list
    prefixSoFar = ''           # accumulator to hold string value
    for char in phrase:
        prefixSoFar += char
        prefixList.append(prefixSoFar)
    
    return prefixList
In [23]:
prefixes('Python')
Out[23]:
['P', 'Py', 'Pyt', 'Pyth', 'Pytho', 'Python']
In [24]:
prefixes('Constantinople')
Out[24]:
['C',
 'Co',
 'Con',
 'Cons',
 'Const',
 'Consta',
 'Constan',
 'Constant',
 'Constanti',
 'Constantin',
 'Constantino',
 'Constantinop',
 'Constantinopl',
 'Constantinople']
In [25]:
prefixes('oh')
Out[25]:
['o', 'oh']

4. The Mapping Pattern

Given a list, we can generate a new list of the same length in which each element is the result of performing the same function on each element of the original list. This is called mapping the function over the list.

In [26]:
def mapDouble(nums):
    """Given a list of numbers, returns a *new* list,
    in which each element is twice the corresponding
    element in the input list.
    """
    result = []
    for n in nums:
        result.append(2*n)
    return result

# test mapDouble with a list of numbers.
mapDouble([42, 19, 57, 36])
Out[26]:
[84, 38, 114, 72]

4.1 Exercise 2: mapSquare

Modify the function mapDouble to become mapSquare, and generate the list of squares for the numbers 1 to 10 (inclusive).

In [27]:
# define mapSquare by modifying mapDouble
def mapSquare(nums):
    """Given a list of numbers, returns a *new* list,
    in which each element is the square of the corresponding
    element in the input list.
    """
    # Flesh out this function body  
    # Your code here
    result = []
    for n in nums:
        result.append(n**2)
    return result
In [28]:
# test mapSquare with a sequence of numbers from 1 to 10 inclusive, 
# use range to generate the list
# Your code here
mapSquare(range(1, 11))
Out[28]:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

4.2 Exercise 3: mapFirstWord

Write a function named mapFirstWord, which takes a list of strings in which each string has words separated by spaces. It returns a new list of the first words in each string. E.g.

mapFirstWord((['feisty smelly dog', 
               'furry white bunny', 
               'orange clown fish']) 
  => ['feisty', 'furry', 'orange']

Reminder: The string method split can split a string into words at spaces.

In [29]:
def mapFirstWord(strings):
    """ Given a list of (possibly multiword) strings, 
    returns a new list in which each element is the first word
    of the corresponding string in the input list. 
    """
    # Flesh out this function body
    # Your code here
    firstWords = []
    for word in strings:
        first = word.split()[0]
        firstWords.append(first)
        
    return firstWords
In [30]:
mapFirstWord (['feisty smelly dog', 'furry white bunny', 'orange clown fish'])
Out[30]:
['feisty', 'furry', 'orange']
In [31]:
mapFirstWord(people)
Out[31]:
['Hermione', 'Harry', 'Ron', 'Luna']

5. The Filtering Pattern

Another common way to produce a new list is to filter an existing list, returning a new list that contains only those elements from the original one that satisfy a certain predicate. This is the filtering pattern.

For example, the filterEvens pattern takes a list of integers and returns new list consisting of only the even numbers in the original list:

In [32]:
def filterEvens(nums):
    """Given a list of numbers, returns a *new* list of all
    numbers in the input list that are divisible by 2.
    """
    result = []
    for n in nums:
        if n%2 == 0:
            result.append(n)
    return result

NOTE: Before executing these cells, try to hypothesize what the output will be, then verify.

In [33]:
filterEvens([100, 21, 32, 44, 55, 71, 91, 23, 56])
Out[33]:
[100, 32, 44, 56]
In [34]:
filterEvens([2, 4, 6, 8])
Out[34]:
[2, 4, 6, 8]
In [35]:
filterEvens([11, 13, 15, 17, 19])
Out[35]:
[]

5.1 Exercise 4: sameFirstLast

Define a function that takes a list of strings and retuns a new list of those strings that begin and end with the same letter. E.g.:

sameFirstLast("I saw a comic who told funny stories".split())
=> ['I', 'a', 'comic', 'stories']
In [36]:
def sameFirstLast(wordList):
    """Return a new list of all words in wordList that begin and end with the same letter.
    """   
    # Flesh out this function body
    # Your code here
    newList = []
    for word in wordList:
        if word[0] == word[-1]:
            newList.append(word)
    return newList
In [37]:
sameFirstLast("I saw a comic who told funny stories".split())
Out[37]:
['I', 'a', 'comic', 'stories']

5.2. Exercise 5: filterElementsContaining

Define a function that takes a string value and a list of strings and returns a new list of all the list elements that contain the string as a substring. E.g.:

filterElementsContaining('er', "The butcher, the baker, the candlestick maker".split())
=> ['butcher', 'baker', 'maker']
In [38]:
def filterElementsContaining(substring, stringList):
    """Return a new list whose elements are all the
    elements of aList that contain val.
    """   
    # Flesh out this function body
    # Your code here
    newList = []
    for string in stringList:
        if substring in string:
            newList.append(string)
    return newList
In [39]:
filterElementsContaining('er', "The butcher, the baker, the candlestick maker".split())
Out[39]:
['butcher,', 'baker,', 'maker']
In [40]:
filterElementsContaining('y',['hairy smelly dog', 'furry white bunny', 'orange clown fish'])
Out[40]:
['hairy smelly dog', 'furry white bunny']

6. Combining Mapping and Filtering

It's easy (and common) to combine the mapping and filtering patterns. E.g. supose we want to return the squares of all the even numbers in a list of integers:

In [41]:
def squaresOfEvens(nums):
    newList = [] 
    for num in nums:
        if num % 2 == 0: # keep only the evens
            newList.append(num*num) # square each of the evens
    return newList

squaresOfEvens(range(1,11))
Out[41]:
[4, 16, 36, 64, 100]