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
while
Loopwhile
Loops With StringscountChar
for
Loops with Stringsfor
Loopsrange
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:
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.
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:
'Boston'[0]
'Boston'[1]
'Boston'[2]
'Boston'[3]
'Boston'[4]
'Boston'[5]
'Boston'[6]
Note that it's an error to use an index that's greater than or equal to the number of elements in the sequence.
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.
def printChars(word):
index = 0
while index < len(word):
print(word[index])
index += 1
printChars('Boston')
printChars('bureaucracies')
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?
isVowel
¶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"
def testIsVowel(char):
"""Helper function to test isVowel"""
print("isVowel('" + char + "')", "=>", isVowel(char))
testIsVowel('d')
testIsVowel('D')
testIsVowel('a')
testIsVowel('E')
testIsVowel('i')
testIsVowel('O')
testIsVowel('u')
testIsVowel('y')
testIsVowel('IOU')
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?
if
statements¶Can you predict the result?
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)
Can you predict the result?
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 )
We always strive to write code that is generic, what will happen when we run it with a new string?
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)
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)
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!
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
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")
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
def testCountChar(char, word):
"""Helper function to test countChar"""
print("countAllVowels('" + char + "', '" + word + "')", "=>", countChar(char, word))
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')
for letter in 'Boston':
print(letter)
A for
loop has the form
for var in sequence:
# Body of for loop
... statements that use var ...
where:
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:
for
loop, letter
is set to 'B'
, so print(letter)
displays B
for
loop, letter
is set to 'o'
, so print(letter)
displays o
for
loop, letter
is set to 's'
, so print(letter)
displays s
for
loop stops after processing the last letter 'n'
of 'Boston'
.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:
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
countVowelsFor('Boston')
countVowelsFor('bureaucracies')
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:
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
countCharFor('banana', 'b') # should be 1
countCharFor('banana', 'a') # should be 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!
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:
[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:
[3.4, "hello", True]
List are values whose type is list
:
type([42, 3, -7])
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.
list("Wendy Wellesley")
Question: Does it work the same way with integers?
list(100)
Question: What if you convert the number into a string first, and then try to turn it into a list. Will that work?
list(str(3.14159265))
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.
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.
for num in [17, 8, 12, 5]:
print('The double of', num, 'is', 2*num)
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)
As with other loops we have seen, we can use for
loops over a list to accumulate values.
def sumList(nums):
sumSoFar = 0
for num in nums:
sumSoFar += num
return sumSoFar
sumList([17, 8, 12, 5])
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).
# Your code here
def sumSmall(nums):
sumSoFar = 0
for num in nums:
if num < 10:
sumSoFar += num
return sumSoFar
sumSmall([17, 8, 12, 5])
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:
#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')
# 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')
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.
range
¶The first argument of range
starts the sequence, and all numbers up to (but not including) the second argument are generated.
range(0, 10)
type(range(0, 10))
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.
list(range(0, 10))
We'll get an empty list when the two arguments are the same, or the first is greater than the second:
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
list(range(8))
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.
# The even numbers
list(range(0, 10, 2))
# Stepping by 10
list(range(3, 70, 10))
The step can be negative. When the step is negative, the start needs to be greater than the end of the range.
list(range(10, 0, -1))
Your turn: Write the range to produce all nonnegative integers up to and including 50 that are divisible by 5:
# Your code here:
nums = range(0,51,5)
list(nums)
We can iterate over this sequence just like any other sequence!
for i in range(11): # Starts at 0, goes up to n-1
print(i)
If we want the range of numbers from 1 up to and including number
, here's one way we can do that:
number = 11
for i in range(number):
print(i + 1) # Add 1 to the number before using it
Remember that we can also explicitly set both the start and end values for range
:
number = 13
for i in range(1, number + 1): # Ask range to give us the right numbers directly
print(i)
Now we can generalize our divisor counting!
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
countDivisors(8)
countDivisors(11)
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
# Your code here
def doublesUpTo(x):
for i in range(x+1):
print(i * 2)
doublesUpTo(4)
You can also use range
to repeat some code multiple times even if you don't use the iteration variable.
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.
To prove that the underscore or using the underscore in a variable name is valid, consider the syntax below:
_ = 3
_
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
# Your code here
def sumIntegers(numOfIntegers):
count = 0
for _ in range(numOfIntegers):
count += int(input("Provide an integer: "))
return count
sumIntegers(3)
sumIntegers(5)
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".
sequence = "computer"
for value in sequence:
print(value)
sequence = [True, None, 3]
for value in sequence:
print(value)
sequence = range(4)
for value in sequence:
print(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?
# 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...
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.
indices = range(len(seq)) # a sequence of indices
list(indices) # list is only being used here to show you the elements in range
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.
# this code prints out all the indices from the original sequence
for index in range(len(seq)):
print(index)
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., []
).
for index in range(len(seq)):
print("Index:", index, "Value:", seq[index])
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.
# 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])
To summarize, let's make index loops for each type of sequence that we know.
sequence = "computer"
for i in range(len(sequence)):
print("Index:", i, "Value:", sequence[i])
sequence = [4.1, 3, 'a']
for i in range(len(sequence)):
print("Index:", i, "Value:", sequence[i])
sequence = range(100, 108, 2)
for i in range(len(sequence)):
print("Index:", i, "Value:", sequence[i])
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:
waitlist = ['BHO', 'MLRO', 'MAO', 'SO', 'AOC', 'NPP', 'RBG']
for name in waitlist:
print(name)
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:
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.
# 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)
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.
word = "Boston"
digits = [1, 2, 3, 4]
digRange = range(1, 5)
word
digits
digRange
Indexing: We access an element by using the [ ]
operator and a number that is the index of the element in the sequence.
word[2]
digits[2]
digRange[2]
Finding length: Because sequences consist of zero or more items, we can use len
to find how many items they contain.
len(word)
len(digits)
len(digRange)
Concatenation: Sequences can be concatenated by using the operators '+' or '*'
word + " Globe"
digits + [4]
Note that concatenation is not supported for range objects.
digRange + range(4)
word * 3
digits * 2
digRange * 2
The membership operator in
: this operator returns True when an item is part of the sequence, and False otherwise
't' in word
'a' in word
'ton' in word
'bos' in word
1 in digits
5 in digits
1 in digRange
5 in digRange
Slicing: this operation uses two indices (a start and a stop) to create a subsequence of items between the two indices.
word[1:4]
digits[1:4]
digRange[1:4]
If the first index is omitted, the start index is by default 0.
word[:3]
digits[:3]
digRange[:3]
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.
word[3:10]
But, we can omit the stop index entirely and get the rest of the subsequence:
word[3:]
We can use a third parameter, step, with the slicing operator. Step works just like step for range, skipping a certain number of items.
word[0:6:2]
digits[0:5:2]
digRange[0:5:2]
We can omit the stop argument as before, and Python automatically will look until the end of the sequence.
digits[0::2]
Reversing through slicing: because Python allows negative indexing (see slide 15), by using step -1, we can reverse a sequence.
word[::-1]
digits[::-1]
digRange[::-1]
From strings to lists
We can create a list from a string using the list
function.
list(word)
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.
phrase = "New England's weather is unpredictable."
phrase.split()
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.
message = "I woke up. The sun rose too. The sky was serene."
message.split('.')
Notice how the character "." was removed and the string was split exactly where the "." was.
Define a function that prints out the squares of every number from 1 to some number x
.
def printSquares(x):
# Your code here
for num in range(1, x + 1):
print(num ** 2)
printSquares(4) # should print 1, 4, 9, 16
printSquares(6) # should print 1, 4, 9, 16, 25, 36
Define a function that prints out all the odd elements in a list.
def printOdd(lst):
# Your code here
for i in range(len(lst)):
if i % 2 == 1:
print(lst[i])
printOdd([4, 'n', 28, 'o']) # should print 'n', 'o'
printOdd(['a', 'b']) # should print '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.
def lastIndex(numList):
# Your code here
lastIndex = -1
for i in range(len(numList)):
num = numList[i]
if num > 100:
lastIndex = i
return lastIndex
lastIndex([1, 3, 102, 4]) # should return 2
lastIndex([103, 102]) # should return 1
lastIndex([1, 2, 3]) # should return -1
Define a function that prints out all the numbers in a list that are both even and at even indices.
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)
evens([0, 4, 5, 7, 8]) # should print 0, 8
evens([1, 2, 3, 4, 5, 6]) # should not print anything
evens([2, 3, 4]) # should print 2, 4
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!
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
isPalindrome("ADA") # should return True
isPalindrome("andy") # should return False
isPalindrome("naan") # should return True
isPalindrome("turtle") # should return False
isPalindrome("able was i ere i saw elba") # should return True
isPalindrome("") # should return True