Lecture: Sequences and Loops

Today we'll talk about sequences, an abstract type that is exemplified by concrete types: such as str, list, and range.
We will talk about the common properties of sequences, and how they are used in enabling another way for looping execution.

Table of Contents

  1. Sequences
  2. Indexing Sequence Elements
  3. Visiting Every Character of a String with a while Loop
  4. Accumulating a Result in while Loops With Strings
  5. Exercise: countChar
  6. for Loops with Strings
  7. A New Kind of Sequence: Lists
  8. Iterating Over Lists with for Loops
  9. Another kind of Sequence: range
  10. Ignoring the Iteration Variable
  11. Value Loops vs. Index Loops
  12. Sequence Operations
  13. Practice

1. Sequences

Sequences in Python are ordered collections of values. We have already seen one example of a sequence: a string. A string is an ordered collection of characters. There are others as well:

Sequences in Python:

  • Strings: an immutable ordered collection of characters
  • Ranges: an immutable ordered collection of integers
  • Lists: a mutable ordered collection of any type
  • Tuples: an immutable ordered collection of any type

We won't talk tuples today, nor will we talk about about mutability vs. immutability. But we will soon!

We often use sequences in conjunctions with loops. Sequences hold data and loops provide a way to look through the sequence one element at a time.

2. Accessing Sequence Elements via Indices

In every kind of sequence, the elements are numbered by an index that goes from 0 up to one less than the number of elements. We can access the element in the sequance at a particular index by wrapping square brackets around the index after a sequence value. For example:

In [1]:
'Boston'[0]
Out[1]:
'B'
In [2]:
'Boston'[1]
Out[2]:
'o'
In [3]:
'Boston'[2]
Out[3]:
's'
In [4]:
'Boston'[3]
Out[4]:
't'
In [5]:
'Boston'[4]
Out[5]:
'o'
In [6]:
'Boston'[5]
Out[6]:
'n'
In [7]:
'Boston'[6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[7], line 1
----> 1 'Boston'[6]

IndexError: string index out of range

Note that it's an error to use an index that's greater than or equal to the number of elements in the sequence.

3. Visiting Every Character of a String with a while Loop

We can use a while loop to visit every character of a string by using a state variable (typically named index) that starts at 0 and increments up to (but not including) the length of the string.

In [8]:
def printChars(word):
    index = 0
    while index < len(word):
        print(word[index])
        index += 1
In [9]:
printChars('Boston')
B
o
s
t
o
n
In [10]:
printChars('bureaucracies')
b
u
r
e
a
u
c
r
a
c
i
e
s

4. Accumulating a Result in while Loops With Strings

In addition to using an index variable to visit every character in a string, we can have an additional state variable that accumulates information about the characters as they are visited.

For example, assume we are given different words, such as "Boston", "Wellesley", "abracadbra", "bureaucracies", and so on, and want to count the number of vowels in each word. How can we do this?

Old friend: isVowel

In [11]:
def isVowel(string):
    """Predicate that returns true only when a string is a single character that's is a vowel."""
    return len(string) == 1 and string.lower() in "aeiou"
In [12]:
def testIsVowel(char):
    """Helper function to test isVowel"""
    print("isVowel('" + char + "')",  "=>",  isVowel(char))
In [13]:
testIsVowel('d')
testIsVowel('D')
testIsVowel('a')
testIsVowel('E')
testIsVowel('i')
testIsVowel('O')
testIsVowel('u')
testIsVowel('y')
testIsVowel('IOU')
isVowel('d') => False
isVowel('D') => False
isVowel('a') => True
isVowel('E') => True
isVowel('i') => True
isVowel('O') => True
isVowel('u') => True
isVowel('y') => False
isVowel('IOU') => False

Sequential vs. Chained conditionals

How can we count the vowels in a word? We can use isVowel and the indices to test each character and keep track of vowels through a counter variable.

But, how do we write the conditionals to test for each character?

Scenario 1: A series of if statements

Can you predict the result?

In [14]:
word = 'Boston'     
vowelCount = 0
if isVowel(word[0]):
    vowelCount += 1
if isVowel(word[1]):
    vowelCount += 1
if isVowel(word[2]):
    vowelCount += 1
if isVowel(word[3]):
    vowelCount += 1
if isVowel(word[4]):
    vowelCount += 1
if isVowel(word[5]):
    vowelCount += 1
print(vowelCount)
2

Scenario 2: Chained conditionals

Can you predict the result?

In [15]:
word = 'Boston'     
vowelCount  = 0
if isVowel(word[0]):
    vowelCount += 1
elif isVowel(word[1]):
    vowelCount += 1
elif isVowel(word[2]):
    vowelCount += 1
elif isVowel(word[3]):
    vowelCount += 1
elif isVowel(word[4]):
    vowelCount += 1
elif isVowel(word[5]):
    vowelCount += 1
print(vowelCount )
1

Try the code with other words

We always strive to write code that is generic, what will happen when we run it with a new string?

In [16]:
word = 'Lynn'     
vowelCount = 0
if isVowel(word[0]):
    vowelCount += 1
if isVowel(word[1]):
    vowelCount += 1
if isVowel(word[2]):
    vowelCount += 1
if isVowel(word[3]):
    vowelCount += 1
if isVowel(word[4]):
    vowelCount += 1
if isVowel(word[5]):
    vowelCount += 1
print(vowelCount)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[16], line 11
      9 if isVowel(word[3]):
     10     vowelCount += 1
---> 11 if isVowel(word[4]):
     12     vowelCount += 1
     13 if isVowel(word[5]):

IndexError: string index out of range
In [17]:
word = 'Wellesley'     
vowelCount = 0
if isVowel(word[0]):
    vowelCount += 1
if isVowel(word[1]):
    vowelCount += 1
if isVowel(word[2]):
    vowelCount += 1
if isVowel(word[3]):
    vowelCount += 1
if isVowel(word[4]):
    vowelCount += 1
if isVowel(word[5]):
    vowelCount += 1
print(vowelCount)
2

Using a while Loop to generalize vowel counting

Remember that a while loop is used to repeat a block of code. What code do we want to repeat? We want to repeat the conditional for each index in the string. As before, we can use a while loop with an index variable to visit every character of the string. But we need the separate vowelCount variable to count the variables along the way. So this loop needs two state variables!

In [18]:
def countVowels(word):
    """Return the number of vowels in word"""
    vowelCount = 0 # the number of vowels seen so far
    index = 0 # the index of the current character
    while index < len(word): # continuation condition
        if isVowel(word[index]):
            vowelCount += 1 # increment vowelCount by one for every vowel seen
        index += 1 # go the index of the next character
    return vowelCount # return the total number of vowels seen
In [19]:
def testCountVowels(word):
    """Testing function for countVowels"""
    print('countVowels("' + word + '") => ' + str(countVowels(word)))

testCountVowels('Boston') 
testCountVowels('Wellesley')  
testCountVowels('Lynn') 
testCountVowels('Manchester-by-the-Sea') 
testCountVowels('bureaucracies') 
testCountVowels("humuhumunukunukuapua'a")
countVowels("Boston") => 2
countVowels("Wellesley") => 3
countVowels("Lynn") => 0
countVowels("Manchester-by-the-Sea") => 6
countVowels("bureaucracies") => 7
countVowels("humuhumunukunukuapua'a") => 12

5. Exercise: countChar

Define a function countChar that takes two arguments -- a character and a word -- and returns the number of times that character appears in the word. Use a while loop to process all the characters in the word.

In [20]:
def countChar(char, word):
    """Return the number of times that char appears in word"""
    # Your code here
    charCount = 0 # the number of times char seen so far
    index = 0 # the index of the current character
    while index < len(word): # continuation condition
        if char == word[index]:
            charCount += 1 # increment charCount by one every time char is seen
        index += 1 # go the index of the next character
    return charCount # return the total number of times char is seen
In [21]:
def testCountChar(char, word):
    """Helper function to test countChar"""
    print("countAllVowels('" + char + "', '" + word + "')",  "=>",  countChar(char, word))
In [22]:
testCountChar('i', 'Mississippi')
testCountChar('p', 'Mississippi')
testCountChar('M', 'Mississippi')
testCountChar('m', 'Mississippi')
testCountChar('-', 'Manchester-by-the-Sea')
testCountChar('t', 'To be or not to be')
testCountChar(' ', 'To be or not to be')
countAllVowels('i', 'Mississippi') => 4
countAllVowels('p', 'Mississippi') => 2
countAllVowels('M', 'Mississippi') => 1
countAllVowels('m', 'Mississippi') => 0
countAllVowels('-', 'Manchester-by-the-Sea') => 3
countAllVowels('t', 'To be or not to be') => 2
countAllVowels(' ', 'To be or not to be') => 5

6. for Loops with Strings

A for loop is a kind of loop that makes it easier to repeat code for every element of a sequence without thinking about indices. Let's start with a simple example:

For loop: print each letter in a string

In [23]:
for letter in 'Boston':
    print(letter)
B
o
s
t
o
n

A for loop has the form

for var in sequence:
    # Body of for loop
    ... statements that use var ...

where:

  • sequence can be any sequence (string, list, etc.)
  • var is an iteration variable that over time will be set will be changed to each element of the sequence from left to right. Every time it is set to the next element, the statements in the loop body will be executed in a context where var contains that element.

In other words, for each element in the sequence, the for loop executes the statements in the body of the loop for that element after setting the iteration variable to that element.

For instance, in the above example:

  • In the first iteration of the for loop, letter is set to 'B', so print(letter) displays B
  • In the second iteration of the for loop, letter is set to 'o', so print(letter) displays o
  • In the third iteration of the for loop, letter is set to 's', so print(letter) displays s
  • and so on, until the for loop stops after processing the last letter 'n' of 'Boston'.

For loop: countVowelsFor

Just like a while loop, a for loop can accumulate a value. But the loop is easier to read because it doesn't need to initialize and increment an index variable. For example, here's a version of countVowels that uses a for loop:

In [24]:
def countVowelsFor(word):
    """A version of countVowels that uses a for loop rather than a while loop"""
    vowelCount = 0 # accumulation variable to count the vowels
    for char in word:
        if isVowel(char):
            vowelCount += 1
    return vowelCount
In [25]:
countVowelsFor('Boston')
Out[25]:
2
In [26]:
countVowelsFor('bureaucracies')
Out[26]:
7

Your turn: Write countCharFor

Below, finish the definition of a version of the countCharFor function that has the same behavior as the countChar function defined earlier, but is defined with a for loop rather than a while loop:

In [27]:
def countCharFor(word, char):
    """Return the number of times that char appears in word.
       Use a for loop rather than a while loop to visit each character in the word.
    """
    # Your code here
    charCount = 0
    for letter in word:
        if letter == char:
            charCount += 1
    return charCount
In [28]:
countCharFor('banana', 'b') # should be 1
Out[28]:
1
In [29]:
countCharFor('banana', 'a') # should be 3
Out[29]:
3

for loops are while loops in disguise!

for loops are another example of the power of abstraction. They simplify loops over sequences by allowing us to focus on the elements of the sequence rather than the indices of those elements.

It turns out that "under the hood", you can imagine that a for loop is first translated into a while loop before it is executed. The pattern

for var in sequence:
    # Body of for loop
    ... statements that use var ...

is translated to something like

index = 0
while index < length(sequence):
    var = sequence[index]
    ... statements that use var ...
    index += 1

You can see from this translation how var is set to each of the elements in the sequence, and how the statements in the loop body are repeated for each element.

When one programming language construct can be rewritten into another, we say that the first is syntactic sugar for the second. In this case, a for loop is syntactic sugar for a while loop. (Alternatively, a for loop desugars to a while loop). Syntactic sugar makes our programming sweeter by hiding tedious details (like explicit indices) that we would prefer not to see. It's an example of abstraction at the level of syntax!

7. A New Kind of Sequence: Lists

for loops iterate over sequences. Strings are an example of a sequence. The for loop assigns each character of the string to the iteration variable. This section discusses a new type called lists which are also sequences.

Whereas as string is an ordered sequence whose elements can only be characters, a list is an order sequence whose elements can be any type of value.

Lists are written with square brackets containing element values that are separated by commas. For example, here is a list of three integers:

In [30]:
[42, 3, -7]
Out[30]:
[42, 3, -7]

Although the type of each element of a list is often the same, they can also be different. Here is a list with a float, a string, and a boolean:

In [31]:
[3.4, "hello", True]
Out[31]:
[3.4, 'hello', True]

List are values whose type is list:

In [32]:
type([42, 3, -7])
Out[32]:
list

Similar to other types that we have seen (int, float, str), there is a built-in function with the same name as the type that generates values of that type by converting values to that type from another type. The function list below converts strings to a list of string characters.

In [33]:
list("Wendy Wellesley")
Out[33]:
['W', 'e', 'n', 'd', 'y', ' ', 'W', 'e', 'l', 'l', 'e', 's', 'l', 'e', 'y']

Question: Does it work the same way with integers?

In [34]:
list(100)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[34], line 1
----> 1 list(100)

TypeError: 'int' object is not iterable

Question: What if you convert the number into a string first, and then try to turn it into a list. Will that work?

In [35]:
list(str(3.14159265))
Out[35]:
['3', '.', '1', '4', '1', '5', '9', '2', '6', '5']

Lists are an example of a data structure, which is a kind of value that holds other values. Lists are a data structure that is used to hold a collection of values in which the order of the values matters.

8. Iterating Over Lists with for Loops

We can use the same syntax for iterating over lists just as we did with strings. With lists, the iteration variable gets assigned to each element in the list.

In [36]:
for num in [17, 8, 12, 5]:
    print('The double of', num, 'is', 2*num)
The double of 17 is 34
The double of 8 is 16
The double of 12 is 24
The double of 5 is 10
In [37]:
books = ["Beloved", "All the light we cannot see", "The Song of Achilles", "Othello"]

print("The book names that begin with a value are:")
for book in books: 
    if isVowel(book[0]):
        print(book)
The book names that begin with a value are:
All the light we cannot see
Othello

As with other loops we have seen, we can use for loops over a list to accumulate values.

In [38]:
def sumList(nums):
    sumSoFar = 0
    for num in nums:
        sumSoFar += num
    return sumSoFar

sumList([17, 8, 12, 5])
Out[38]:
42

Your Turn: sumSmall

Below write a function called sumSmall that returns the sum of all "small" numbers in a list of numbers. A number is considered small if it's less than 10. E.g. sumSmall([17, 8, 12, 5]) should return 13 (= 8 + 5).

In [39]:
# Your code here
def sumSmall(nums):
    sumSoFar = 0
    for num in nums:
        if num < 10: 
            sumSoFar += num
    return sumSoFar

sumSmall([17, 8, 12, 5])
Out[39]:
13

9. Another kind of Sequence: range

Strings and lists are sequences. Another type of sequence is a range, which is created by the built-in function range. This function generates a sequence of ordered numbers. To see why range can be useful, consider the example below:

In [40]:
#Let's check how many divisors a number has.
number = 8
divisors = 0
for candidate in [1,2,3,4,5,6,7,8]:
    if number % candidate == 0:
        divisors = divisors + 1
print(number, 'has', divisors, 'divisors')
8 has 4 divisors
In [41]:
# And again...
number = 11
divisors = 0
for candidate in [1,2,3,4,5,6,7,8,9,10,11]:
    if number % candidate == 0:
        divisors = divisors + 1
print(number, 'has', divisors, 'divisors')
11 has 2 divisors

Notice that we have to hardcode the list of numbers. This means we can't use this code on any number. We'll learn about a new tool that lets us generalize our divisor counting now.

Creating sequences of numbers with the built-in function range

The first argument of range starts the sequence, and all numbers up to (but not including) the second argument are generated.

In [42]:
range(0, 10)
Out[42]:
range(0, 10)
In [43]:
type(range(0, 10))
Out[43]:
range

The range function returns an object that holds the sequence. To see the contents of range's sequence, pass it into the built-in list function that will return a list of numbers. We will talk more about the list function in subsequent classes.

In [44]:
list(range(0, 10))
Out[44]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

We'll get an empty list when the two arguments are the same, or the first is greater than the second:

In [45]:
a = range(3, 3)
b = range(3, 1)
print(list(a))
print(list(b))
[]
[]

Omiting the first argument means we want to start at 0 by default

In [46]:
list(range(8))
Out[46]:
[0, 1, 2, 3, 4, 5, 6, 7]

Using range with three arguments

range takes a third argument, the value of the step between two generated values. If this value is missing, by default step is 1.

In [47]:
# The even numbers
list(range(0, 10, 2))
Out[47]:
[0, 2, 4, 6, 8]
In [48]:
# Stepping by 10
list(range(3, 70, 10))
Out[48]:
[3, 13, 23, 33, 43, 53, 63]

The step can be negative. When the step is negative, the start needs to be greater than the end of the range.

In [49]:
list(range(10, 0, -1))
Out[49]:
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Your turn: Write the range to produce all nonnegative integers up to and including 50 that are divisible by 5:

In [50]:
# Your code here:
nums = range(0,51,5)
list(nums)
Out[50]:
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

We can iterate over this sequence just like any other sequence!

In [51]:
for i in range(11):     # Starts at 0, goes up to n-1
    print(i)
0
1
2
3
4
5
6
7
8
9
10

If we want the range of numbers from 1 up to and including number, here's one way we can do that:

In [52]:
number = 11
for i in range(number):
    print(i + 1)                # Add 1 to the number before using it
1
2
3
4
5
6
7
8
9
10
11

Remember that we can also explicitly set both the start and end values for range:

In [53]:
number = 13
for i in range(1, number + 1):   # Ask range to give us the right numbers directly
    print(i)
1
2
3
4
5
6
7
8
9
10
11
12
13

Now we can generalize our divisor counting!

In [54]:
def countDivisors(number):
    """A function that returns the total of divisors for a given number.
    """
    divisors = 0
    for candidate in range(1, number + 1):
        if number % candidate == 0:
            divisors = divisors + 1
    return divisors
In [55]:
countDivisors(8)
Out[55]:
4
In [56]:
countDivisors(11)
Out[56]:
2

Your Turn: Write doublesUpTo

Write a function doublesUpTo which takes a number x and prints out each of the integers up to and including x doubled. For example, doublesUpTo(4) should print out

0
2
4
6
8
In [57]:
# Your code here
def doublesUpTo(x):
    for i in range(x+1):
        print(i * 2)
        
doublesUpTo(4)
0
2
4
6
8

10. Ignoring the Iteration Variable

You can also use range to repeat some code multiple times even if you don't use the iteration variable.

In [58]:
for _ in range(10):                                    # The variable name _ is just a normal variable name, 
    print('Say this tongue twister ten times fast!')   # but some programmers use it as a convention to indicate to
                                                       # a reader of the code that syntax requires a variable name 
                                                       # but they don't plan to use the value stored in that variable.
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!
Say this tongue twister ten times fast!

To prove that the underscore or using the underscore in a variable name is valid, consider the syntax below:

In [59]:
_ = 3
In [60]:
_
Out[60]:
3

Your Turn: Sum Integers

Write a function called sumIntegers that takes a number numOfInts specifying the number of integers to sum and prompts the user numOfInts times to provide an integer. The function should return the sum of all those integers. Here is a sample call below.

In [1]: sumIntegers(3)
Provide an integer: 4
Provide an integer: 2
Provide an integer: 1
Out [1]: 7
In [61]:
# Your code here

def sumIntegers(numOfIntegers):
    count = 0
    for _ in range(numOfIntegers):
        count += int(input("Provide an integer: "))
    return count
In [62]:
sumIntegers(3)
Provide an integer: 4
Provide an integer: 2
Provide an integer: 1
Out[62]:
7
In [63]:
sumIntegers(5)
Provide an integer: 2
Provide an integer: 57
Provide an integer: 6
Provide an integer: 42
Provide an integer: 4
Out[63]:
111

11. Value Loops vs. Index Loops

Up to this point, we have seen how for loops iterate over a sequence by setting the iteration variable to each value in the sequence. For a string, the iteration variable is set to each letter in the string. For a list, the iteration variable is set to each element in the list. For a range, the iteration variable is set to each number in the range. All of the for loops we have examined so far are meant to extract each value from the sequence. When we iterate directly over a sequence like this, we call it a "value loop".

Value Loop with Strings

In [64]:
sequence = "computer"
for value in sequence:
    print(value)
c
o
m
p
u
t
e
r

Value Loop with Lists

In [65]:
sequence = [True, None, 3]
for value in sequence:
    print(value)
True
None
3

Value Loop with Ranges

In [66]:
sequence = range(4)
for value in sequence:
    print(value)
0
1
2
3

Problem: How do we get the position of each value?

While the for loop patterns we have seen so far help us solve many problems, they do not encompass all the ways we can use loops. For example, how would we write a loop that prints out every other element in a list? We can, of course, use a while loops as we have seen before, but how could we do it with a for loop?

In [67]:
# How can I print out all the letters from this list
seq = ['a', 1, 'b', 2, 'c', 3]
for element in seq:
    print(element) # this prints out every element...
a
1
b
2
c
3

The position of elements is important in solving this problem. The loop above only gives us the values in the sequence. We need more information! We need the positions of the values!

How can I get the position of the elements? Here we can use range and len to help us. The solution to this problem is to generate a new sequence whose elements are the indices of the original sequence. For example, given the list ['a', 1, 'b', 2, 'c', 3], we want to be able to generate a sequence that has the numbers 0, 1, 2, 3, 4, and 5.

In [68]:
indices = range(len(seq)) # a sequence of indices

list(indices) # list is only being used here to show you the elements in range
Out[68]:
[0, 1, 2, 3, 4, 5]

Now that we have a sequence of the indices, we will loop over the indices and set the iteration variable to each index in the sequence of indices. We call this an index loop because we set the iteration variable to each index in the original sequence as opposed to its value.

In [69]:
# this code prints out all the indices from the original sequence

for index in range(len(seq)):
    print(index)
0
1
2
3
4
5

The great thing about indices is that we can also use the index to get value at that index as well using the subscripting operator (i.e., []).

In [70]:
for index in range(len(seq)):
    print("Index:", index, "Value:", seq[index])
Index: 0 Value: a
Index: 1 Value: 1
Index: 2 Value: b
Index: 3 Value: 2
Index: 4 Value: c
Index: 5 Value: 3

Now that we know how to generate the indices from any sequence, we can solve our original problem. Note here that we use the iteration variable i. This is a common choice for index loops as i indicates an index. We can, of course, choose something else but this is a common convention.

In [71]:
# a loop that prints only the letters from the list ['a', 1, 'b', 2, 'c', 3]

for i in range(len(seq)):
    if i % 2 == 0: # we use the index to help us filter out the positions we care about
        print(seq[i])
a
b
c

To summarize, let's make index loops for each type of sequence that we know.

Index Loop with Strings

In [72]:
sequence = "computer"
for i in range(len(sequence)):
    print("Index:", i, "Value:", sequence[i])
Index: 0 Value: c
Index: 1 Value: o
Index: 2 Value: m
Index: 3 Value: p
Index: 4 Value: u
Index: 5 Value: t
Index: 6 Value: e
Index: 7 Value: r

Index Loop with Lists

In [73]:
sequence = [4.1, 3, 'a']
for i in range(len(sequence)):
    print("Index:", i, "Value:", sequence[i])
Index: 0 Value: 4.1
Index: 1 Value: 3
Index: 2 Value: a

Index Loop with Ranges

In [74]:
sequence = range(100, 108, 2)
for i in range(len(sequence)):
    print("Index:", i, "Value:", sequence[i])
Index: 0 Value: 100
Index: 1 Value: 102
Index: 2 Value: 104
Index: 3 Value: 106

Another Example using Index Loops

Sometimes your loop needs both the values in a sequence as well as their indices. For example, let's imagine that you're running an amusement park. When people want to get on a ride, they put their initials on a waitlist. Then, you display the waitlist on a screen so folks can know how close to the front of the line they are. So for example, if some prominent American figures wanted to ride, the screen might show something like:

#0: BHO
#1: MLRO
#2: MAO
#3: SO
#4: AOC
#5: NPP
#6: RBG

Our first attempt might look like this:

In [75]:
waitlist = ['BHO', 'MLRO', 'MAO', 'SO', 'AOC', 'NPP', 'RBG']
for name in waitlist:
    print(name)
BHO
MLRO
MAO
SO
AOC
NPP
RBG

But this doesn't print the indices, which are an important piece of information that we wanted to display. In this case, we will often use range to generate indices, like so:

In [76]:
waitlist = ['BHO', 'MLRO', 'MAO', 'SO', 'AOC', 'NPP', 'RBG']
for spot in range(len(waitlist)):           # Range generates the indices
    name = waitlist[spot]                   # Use indexing to get the value
    print('#' + str(spot) + ': ' + name)    # Perform a calculation using both value and index.
#0: BHO
#1: MLRO
#2: MAO
#3: SO
#4: AOC
#5: NPP
#6: RBG
In [77]:
# Once Barack gets off the list and is on the rollercoaster, you can see that everyone else has moved up
waitlist = ['MLRO', 'MAO', 'SO', 'AOC', 'NPP', 'RBG']
for spot in range(len(waitlist)):
    name = waitlist[spot]
    print('#' + str(spot) + ': ' + name)
#0: MLRO
#1: MAO
#2: SO
#3: AOC
#4: NPP
#5: RBG

12. Sequence Operations

A sequence, is a series of items for which the relative order to one-another matters. In Python, a sequence is the parent class for strings, lists, and ranges. This way, all of these classes will share their behavior (in terms of what operations can be applied to them), but they also will have differences, which we will discuss in the coming lectures.

In [78]:
word = "Boston"
digits = [1, 2, 3, 4]
digRange = range(1, 5)
In [79]:
word
Out[79]:
'Boston'
In [80]:
digits
Out[80]:
[1, 2, 3, 4]
In [81]:
digRange
Out[81]:
range(1, 5)

Indexing: We access an element by using the [ ] operator and a number that is the index of the element in the sequence.

In [82]:
word[2]
Out[82]:
's'
In [83]:
digits[2]
Out[83]:
3
In [84]:
digRange[2]
Out[84]:
3

Finding length: Because sequences consist of zero or more items, we can use len to find how many items they contain.

In [85]:
len(word)
Out[85]:
6
In [86]:
len(digits)
Out[86]:
4
In [87]:
len(digRange)
Out[87]:
4

Concatenation: Sequences can be concatenated by using the operators '+' or '*'

In [88]:
word + " Globe"
Out[88]:
'Boston Globe'
In [89]:
digits + [4]
Out[89]:
[1, 2, 3, 4, 4]

Note that concatenation is not supported for range objects.

In [90]:
digRange + range(4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[90], line 1
----> 1 digRange + range(4)

TypeError: unsupported operand type(s) for +: 'range' and 'range'
In [91]:
word * 3
Out[91]:
'BostonBostonBoston'
In [92]:
digits * 2
Out[92]:
[1, 2, 3, 4, 1, 2, 3, 4]
In [93]:
digRange * 2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[93], line 1
----> 1 digRange * 2

TypeError: unsupported operand type(s) for *: 'range' and 'int'

The membership operator in: this operator returns True when an item is part of the sequence, and False otherwise

In [94]:
't' in word
Out[94]:
True
In [95]:
'a' in word
Out[95]:
False
In [96]:
'ton' in word
Out[96]:
True
In [97]:
'bos' in word
Out[97]:
False
In [98]:
1 in digits
Out[98]:
True
In [99]:
5 in digits
Out[99]:
False
In [100]:
1 in digRange
Out[100]:
True
In [101]:
5 in digRange
Out[101]:
False

Slicing: this operation uses two indices (a start and a stop) to create a subsequence of items between the two indices.

In [102]:
word[1:4]
Out[102]:
'ost'
In [103]:
digits[1:4]
Out[103]:
[2, 3, 4]
In [104]:
digRange[1:4]
Out[104]:
range(2, 5)

If the first index is omitted, the start index is by default 0.

In [105]:
word[:3]
Out[105]:
'Bos'
In [106]:
digits[:3]
Out[106]:
[1, 2, 3]
In [107]:
digRange[:3]
Out[107]:
range(1, 4)

If the stop index is greater than the length of the sequence, Python doesn't return an error, it returns the sequence until the end.

In [108]:
word[3:10]
Out[108]:
'ton'

But, we can omit the stop index entirely and get the rest of the subsequence:

In [109]:
word[3:]
Out[109]:
'ton'

We can use a third parameter, step, with the slicing operator. Step works just like step for range, skipping a certain number of items.

In [110]:
word[0:6:2]
Out[110]:
'Bso'
In [111]:
digits[0:5:2]
Out[111]:
[1, 3]
In [112]:
digRange[0:5:2]
Out[112]:
range(1, 5, 2)

We can omit the stop argument as before, and Python automatically will look until the end of the sequence.

In [113]:
digits[0::2]
Out[113]:
[1, 3]

Reversing through slicing: because Python allows negative indexing (see slide 15), by using step -1, we can reverse a sequence.

In [114]:
word[::-1]
Out[114]:
'notsoB'
In [115]:
digits[::-1]
Out[115]:
[4, 3, 2, 1]
In [116]:
digRange[::-1]
Out[116]:
range(4, 0, -1)

From strings to lists

We can create a list from a string using the list function.

In [117]:
list(word)
Out[117]:
['B', 'o', 's', 't', 'o', 'n']

We can also produce a list by splitting a string at spaces. This is commonly use to split a sentence into a list of words.

In [118]:
phrase = "New England's weather is unpredictable."
phrase.split()
Out[118]:
['New', "England's", 'weather', 'is', 'unpredictable.']

When the split method doesn't take arguments, it splits by default at the white space. If needed, you can split at some other character.

In [119]:
message = "I woke up. The sun rose too. The sky was serene."
message.split('.')
Out[119]:
['I woke up', ' The sun rose too', ' The sky was serene', '']

Notice how the character "." was removed and the string was split exactly where the "." was.

13. Practice

Define a function that prints out the squares of every number from 1 to some number x.

In [120]:
def printSquares(x):
    # Your code here
    for num in range(1, x + 1):
        print(num ** 2)
In [121]:
printSquares(4) # should print 1, 4, 9, 16
1
4
9
16
In [122]:
printSquares(6) # should print 1, 4, 9, 16, 25, 36
1
4
9
16
25
36

Define a function that prints out all the odd elements in a list.

In [123]:
def printOdd(lst):
    # Your code here
    for i in range(len(lst)):
        if i % 2 == 1:
            print(lst[i])
In [124]:
printOdd([4, 'n', 28, 'o']) # should print 'n', 'o'
n
o
In [125]:
printOdd(['a', 'b']) # should print 'b'
b

Define a function that takes a list of numbers and returns the index of the last number greater than 100. If no numbers exist greater than 100, return -1.

In [126]:
def lastIndex(numList):
    # Your code here
    lastIndex = -1
    for i in range(len(numList)):
        num = numList[i]
        if num > 100:
            lastIndex = i
    return lastIndex
In [127]:
lastIndex([1, 3, 102, 4]) # should return 2
Out[127]:
2
In [128]:
lastIndex([103, 102]) # should return 1
Out[128]:
1
In [129]:
lastIndex([1, 2, 3]) # should return -1
Out[129]:
-1

Define a function that prints out all the numbers in a list that are both even and at even indices.

In [130]:
def evens(numList):
    # Your code here
    for i in range(len(numList)):
        num = numList[i]
        if i % 2 == 0 and num % 2 == 0:
            print(num)
In [131]:
evens([0, 4, 5, 7, 8]) # should print 0, 8
0
8
In [132]:
evens([1, 2, 3, 4, 5, 6]) # should not print anything
In [133]:
evens([2, 3, 4]) # should print 2, 4
2
4

Challenge: isPalindrome

Write a function that checks whether a string is a palindrome. A palindrome is a word or phrase that reads the same backwards or forwards. The function should return True if it the string is a palindrome and False otherwise.

Note that this is pretty tricky!

In [134]:
def isPalindrome(phrase):
    # Your code here
    for i in range(len(phrase)):
        let = phrase[i]
        endLet = phrase[len(phrase) - i - 1]
        if let != endLet:
            return False
    return True
In [135]:
isPalindrome("ADA") # should return True
Out[135]:
True
In [136]:
isPalindrome("andy") # should return False
Out[136]:
False
In [137]:
isPalindrome("naan") # should return True
Out[137]:
True
In [138]:
isPalindrome("turtle") # should return False
Out[138]:
False
In [139]:
isPalindrome("able was i ere i saw elba") # should return True
Out[139]:
True
In [140]:
isPalindrome("") # should return True
Out[140]:
True
In [ ]: