Lists and Nested Loops (2)¶
List Accumulations¶
It is often the case that we use loops to iterate over a sequence to accumulate certain items from it. For example, we may want to collect all the words from a wordList that begin with a vowel (or have some other interesting property). How would we approach this problem?
First we need to be able to iterate over all words in the original list.
Then, for each word we must check to see if it has the property we are looking for.
If it does, we want to remember it: by storing it somewhere.
Since we want to store a collection of such words, we can use a list type. If we were interested in returning a string (e.g., in
vowelSeq
, we would collect the characters in a string accumulation variable.)
Such processes where we are accumulating something while iterating over sequences call for the use of an appropriate accumulation variable. It is important to initialize these accumulation variables before the loop.
Exercise: palindromes
¶
Write a function that iterates over a given list of strings sList
, and returns a (new) list containing all the strings in sList
that are palindromes (read the same backward and forward).
Expected behavior:
>>> palindromes(['Anna', 'banana', 'kayak', 'rigor', 'tacit', 'hope'])
['Anna', 'kayak']
>>> palindromes(['1313', '1110111', '0101'])
['1110111']
>>> wordStartEnd(['Level', 'Stick', 'Gag'])
['Level', 'Gag']
def palindromes(sList):
'''Takes a list of words and returns a new list of words comprised
of words from the original list that are palindromes'''
pass
def palindromes(sList):
'''Takes a list of words and returns a new list of words comprised
of words from the original list that are palindromes'''
# initialize accumulation variable (of type list)
result = []
for word in sList: # iterate over list
wLower = word.lower() #ignore case
if wLower[::-1] == wLower: # [::-1] returns wLower in reverse
result += [word] # concatenate to resulting list, notice []
return result
palindromes(['Anna', 'banana', 'kayak', 'rigor', 'tacit', 'hope'])
['Anna', 'kayak']
palindromes(['1313', '1110111', '0101'])
['1110111']
palindromes(['Level', 'Stick', 'Gag'])
['Level', 'Gag']
Nested Loops¶
We can put for loops
inside other for loops: this is called nested loops
.
Let us see some examples.
# What does this do?
def mysteryPrint(word1, word2):
"""Prints something"""
for char1 in word1:
for char2 in word2:
print(char1, char2)
mysteryPrint('123', 'abc')
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
3 c
# What does this print?
for letter in ['b','d','r','s']:
for suffix in ['ad', 'ib', 'ump']:
print(letter + suffix)
bad
bib
bump
dad
dib
dump
rad
rib
rump
sad
sib
sump
Review: Useful String/List methods¶
It is often useful to be able to convert strings to lists, and lists to strings.
.split()
method. When the split()
method is called on a string of words, it breaks up the words (by default at blank spaces) and returns a list of the words
.join()
method. Given a list of strings, the .join()
string method, when applied to a character char
, concatenates the strings in the list together with the character char
between them and returns a string
.strip()
method. strip()
remove leading and trailing whitespace characters (including blank spaces and new lines) from a string and returns a new string
phrase = "What a lovely day"
phrase.split()
['What', 'a', 'lovely', 'day']
newPhrase = "What a *lovely* day!" # multiple spaces or punctuations dont matter
newPhrase.split()
['What', 'a', '*lovely*', 'day!']
commaSepSpells = "Impervius, Portus, Lumos, Reducio, Protego" #comma separated strings
commaSepSpells.split(',')
['Impervius', ' Portus', ' Lumos', ' Reducio', ' Protego']
wordList = ['Everybody', 'is', 'looking', 'forward', 'to', 'the', 'weekend']
'*'.join(wordList)
'Everybody*is*looking*forward*to*the*weekend'
'_'.join(wordList)
'Everybody_is_looking_forward_to_the_weekend'
' '.join(wordList)
'Everybody is looking forward to the weekend'
word = " ****Snowy Winters** "
word.strip()
'****Snowy Winters**'
word = '\nHello world\n'
word
'\nHello world\n'
word.strip()
'Hello world'
Range sequences¶
Python provides an easy way to iterate over common numerical sequences through the range
data type. We create ranges using the range()
function.
range(0,10)
range(0, 10)
type(range(0, 10))
range
In Python 3, we don’t have an easy way to display the numbers stored in a range object. If we want to examine the contents of a range object, we can pass the object into the function list()
which returns a list of the numbers in the range object.
Similar to other types that we have seen, such as integers, floats and strings, the built-in function list() converts values and other data types into a list.
Using list()
on range objects:
The list()
function, when given a range object, returns a list of the elements in that range. This is convenient for see what a range object actually consists of.
list(range(0, 10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Notice. The range(firNum, secNum)
represents all numbers from firNum
through secNum - 1.
If the firNum
is 0, we can omit. For example:
list(range(-10, 10))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(range(3))
[0, 1, 2]
Using list()
on strings:
When applied to a string, list()
converts the string into a list of string its constituent string characters (in order).
spell = "Avada Kedavra!"
list(spell) # can turn a string into a list of its characters
['A', 'v', 'a', 'd', 'a', ' ', 'K', 'e', 'd', 'a', 'v', 'r', 'a', '!']
list(str(3.14159265))
['3', '.', '1', '4', '1', '5', '9', '2', '6', '5']
Appending to Lists¶
There are two ways to add items to a list. We have already seen how we can concatenate with the + operator. This actually creates a new list and returns it, similar to string concatenation. Unlike strings, however, lists are mutable, which means they can be modified. To add an item to an existing list, we can use the list append()
method.
numList = [1, 2, 3, 4, 5]
numList + [6]
[1, 2, 3, 4, 5, 6]
numList # numList has not changed
[1, 2, 3, 4, 5]
numList.append(6)
numList # numList has been updated to include 6
[1, 2, 3, 4, 5, 6]
Looping over ranges¶
Range functions provide us with an iterable sequence, which we can loop over, just like we did with strings and list.
What are some reasons why we might want to loop over a range of numbers?
for i in range(1, 11): # simple for loop that prints numbers 1-10
print(i)
1
2
3
4
5
6
7
8
9
10
# what does this print?
for i in range(5):
print('$' * i)
for j in range(5):
print('*' * j)
$
$$
$$$
$$$$
*
**
***
****
# what does this print?
for i in range(5):
print('$' * i)
for j in range(i):
print('*' * i)
$
*
$$
**
**
$$$
***
***
***
$$$$
****
****
****
****
for _ in range(10):
print('Hello World!')
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Testing Functions¶
Suppose we want to test a function we have written. There are several ways to do so. You can test using interactive Python by importing the function and checking to see if it returns the correct output when called on a bunch of different values.
Testing using doctests. Python’s doctest module allows you to embed your test cases and expected output directly into a function’s docstring. To use the doctest module, we must import the doctest module, and invoke the doctests using doctest.testmod(). To make sure that the tests are not run in an interactive shell or when the functions from the module are imported, we should place the aforementioned command within a guarded if __name__ == "__main__":
block. See slides for more explanation.
Doctests module¶
The doctest
module searches for pieces of text that look like interactive Python sessions inside of docstrings, and then executes those tests to verify that the output matches.
def isVowel(char):
"""Predicate that returns true only when a letter is a vowel.
>>> isVowel('d')
False
>>> isVowel('e')
True
"""
return char.lower() in 'aeiou'
import doctest
doctest.testmod(verbose = True)
# Task: try this out as a script and
# run from the terminal
Trying:
isVowel('d')
Expecting:
False
ok
Trying:
isVowel('e')
Expecting:
True
ok
3 items had no tests:
__main__
__main__.mysteryPrint
__main__.palindromes
1 items passed all tests:
2 tests in __main__.isVowel
2 tests in 4 items.
2 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=2)