Lists and Loops

A New Sequence: Lists

Recall that sequences are an abstract type in Python that represent ordered collections of elements.

Last lecture we focused on strings, today we will discuss lists.

Unlike strings, which are a homogenous sequence of characters, lists can be a collection of heterogenous objects.

# Examples of various lists:
    
wordList = ['What', 'a', 'beautiful', 'day']
numList = [1, 5, 8, 9, 15, 27]
charList = ['a', 'e', 'i', 'o', 'u'] 
mixedList = [3.145, 'hello', 13, True] # lists can be heterogeous
type(numList)
list

Sequence Operators and Functions

We saw several operators and functions that apply to sequences when we discussed strings. They apply to lists as well.

  • Indexing elements of lists using []

  • Using len function to find length

  • Slicing lists using [:]

  • Testing membership using the in and not in operators

  • Concatenation using +

Recall that the in operator tests membership and returns True if and only if an element is in a sequence. On the other hand, the not in operator returns True if and only if a given element is not in the sequence.

Note that it is preferable and more readable to say if el not in seq compared to the (logically equivalent) if not el in seq.

wordList = ['What', 'a', 'beautiful', 'day']

wordList[3]
'day'
wordList[-1]
'day'
len(wordList)
4
nameList = ["Anna", "Beth", "Chris", "Daxi", "Emory", "Fatima"]
nameList[2:4]
['Chris', 'Daxi']
wordList[::-1] # what will this do?
['day', 'beautiful', 'a', 'What']
nameList = ["Aamir", "Beth", "Chris", "Daxi", "Emory"]
"Anna" in nameList # test membership
False
"Jeannie" in nameList
False
"Jeannie" not in nameList # not in returns true if el not in seq
True
"a" not in "Chris" # also works on strings
True
aList = ['the', 'quick', 'brown', 'fox']
bList = ['jumped', 'over', 'the', 'dogs']
aList + bList # concatenate lists
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'dogs']
aList # aList is unchanged
['the', 'quick', 'brown', 'fox']
bList = bList + ['back'] # add 'back' to bList
bList # since we reassign result to bList, bList has changed
['jumped', 'over', 'the', 'dogs', 'back']

Looping over Lists

We can use a for loop to iterate over the elements of a list just as we did with strings.

numList = [0, 2, 4, 6, 8, 10]
for num in numList:
    print(num)
0
2
4
6
8
10

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 if it has the property we are looking for.

  • If it does, we may want to count it, or remember it by storing it somewhere.

  • If we want to store it, we can use a list as the accumulation variable. 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: countItem function

Write a function countItem that takes as input a sequence seq (can be a string or a list), and an element el and returns the number of times el appears in the sequence seq. Here your accumulation variable should be an integer.

def countItem(seq, el):
    """Takes list of sequence seq, and returns
    the number of times el appears"""
    pass
def countItem(seq, el):
    """Takes list of sequence seq, and returns
    the number of times el appears"""
    # initialize accumulation variable
    count = 0
    for item in seq:
        # check to see if the next item in seq is el
        if item == el:
            count += 1 # increment count
    return count # return count after for loop ends
countItem([3, 2, 2, 3, 2, 1, 4], 2)
3
countItem("onomatopoeia", "o")
4
countItem("Alabama", "a")
3
countItem(["apple", "banana", "orange"], "kiwi")
0

Exercise: wordStartEnd

Write a function that iterates over a given list of words wordList and returns a (new) list containing all the words in wordList that start and end with the same letter (ignoring case). Your accumulation variable should be a list.

Expected behavior:

>>> wordStartEnd(['Anna', 'banana', 'salad', 'Rigor', 'tacit', 'hope'])
['Anna', 'Rigor', 'tacit']
>>> wordStartEnd(['New York', 'Tokyo', 'Paris'])
[]
>>> wordStartEnd(['*Hello*', '', 'nope'])
['*Hello*']
def wordStartEnd(wordList):
    '''Takes a list of words and returns the list of words in it
    that start and end with the same letter'''
    pass 
def wordStartEnd(wordList):
    '''Takes a list of words and returns the list of words in it
    that start and end with the same letter'''
    # initialize accumulation variable (of type list)
    result = []
    for word in wordList: # iterate over list
        #check for empty strings before indexing
        if len(word) != 0:   
            if word[0].lower() == word[-1].lower():
                result += [word] # concatenate to resulting list
    return result # notice the indentation of return
            
wordStartEnd(['Anna', 'banana', 'salad', 'Rigor', 'tacit', 'hope'])
['Anna', 'Rigor', 'tacit']
wordStartEnd(['Scared', 'Sure', 'Sars', 'Viral', 'viv', 'stills'])
['Sars', 'viv', 'stills']
wordStartEnd(['New York', 'Tokyo', 'Paris'])
[]
wordStartEnd(['*Hello*', '', 'nope'])
['*Hello*']
wordStartEnd(['Exude', 'Eerie', 'Soup', 'knack', 'snack'])
['Exude', 'Eerie', 'knack']

Exercise: palindromes

Write a function that iterates over a given list of strings sList, 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 counts the 
    number of words in it that start and end 
    with the same letter'''
    pass 
def palindromes(sList):
    '''Takes a list of words and counts the 
    number of words in it that start and end 
    with the same letter'''
    # initialize accumulation variable (of type list)
    result = []
    for word in sList: # iterate over list
        wLower = word.lower()
        if wLower[::-1] == wLower:  
            result += [word] # concatenate to resulting list
    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

An Aside: Testing Functions

Suppose we want to test a function we have written. There are several ways to do so. You can test using interactively python by importing the function from 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 functions docstring. To use the doctest module we must import it. To make sure the test cases are run when the program is run as a script from the terminal, we need to insert a 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 command within a guarded if __name__ == "__main__": block.

You’ll get to practice using doctests in Lab 3.

Doctests module

The doctest module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.

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
5 items had no tests:
    __main__
    __main__.countItem
    __main__.mysteryPrint
    __main__.palindromes
    __main__.wordStartEnd
1 items passed all tests:
   2 tests in __main__.isVowel
2 tests in 6 items.
2 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=2)