# CS111 Lecture: List Processing Patterns¶

Table of Contents:

## 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]