7 Tuples, Lists, and Dictionaries
Notebook 7 - Tuples, Lists, and Dictionaries¶
Tuples, Lists, and Dictionaries
Make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- mutability
- sequences
- tuples
- lists
- dictionaries
Mutability¶
Some data types can be changed (mutable) and some cannot. For example, strings are immutable. For this reason, you cannot assign values to the characters in a string. And when you call a function that seems to modify a string, it actually creates a new string and (normally) disposes of the old one.
s = 'Susan X. Anthony'
print('sixth char:', s[6])
s[6] = 'B'
print(s)
s = 'Susan B. Anthony'
print(s)
Tuples¶
Tuples are ordered collections, i.e. sequences, like strings, but they can contain any type of value (not just characters). They can even contain different types within the same tuple. Tuples are defined by parentheses (i.e. brackets) and the elements of a tuple are separated by commas, like this:
('a', 'b', 'c', 1, 2, 3)
.
Creating Tuples¶
# Create an empty tuple
tup = ()
print('empty tuple:', tup)
# Create a tuple with some initial contents
tup = ('Python', 1024, 3.14, True)
print('non-empty tuple:', tup)
# the same value can occur multiple times in a tuple
tup = ('a', 'a', 'a')
print(tup)
Tuple Operations¶
Because tuples are sequences, like strings, functions and for loops that operate on strings also play well with tuples, for example:
# the len function returns the length of a tuple
tup = (1, 2, 3, 4)
print(len(tup)) # returns size of a tuple
tup = (1, 2, 3, 4, 5)
print(len(tup))
# indexing (like strings, tuple offsets start with zero!)
tup = (1, 2, 3)
print(tup[0])
print()
print('the whole tuple...')
print(tup)
# indexing out of bounds raises a runtime error
tup = ('abc', 123, 3.14, True)
print(tup[99])
# loops
tup = (1, 2, 3)
for i in range(len(tup)):
print(tup[i])
# a better way to loop (a.k.a. iterate) over tuples...
tup = (1, 2, 3)
for i in tup:
print(i)
# the in operator (membership test)
x = 4
tup = (1, 2, 3, 4, 5)
if x in tup: # True if var’s value is in tuple
print(tup, 'contains ', x)
else:
print(tup, 'does NOT contain', x)
# slicing
tup = (1, 2, 3, 4, 5)
print(tup[2:]) # prints 3rd through end of tuple
print(tup[:3]) # prints first through third from last
print(tup[2:4])
# the plus operator concatenates (combines) two tuples into one
t1 = (1, 2, 3)
t2 = (4, 5, 6)
print(t1 + t2)
Tuples are Immutable¶
Like strings, once created, you can't change a tuple.
tup = (1, 2, 3)
print(tup[1])
tup[1] = 7
But you can assign a new tuple to the same variable. You haven't changed the tuple, you've changed the association between a variable and it's value.
tup = (1, 2, 3)
print(tup)
tup = (1, 3, 2)
# the tuple didn't change, the tup variable now points to different data!
print(tup)
Nested Tuples¶
Just as you can have if statements of if statements (nested if statements), and loops of loops (nested loops). You can have also have tuples of tuples (nested tuples).
- tuple of tuples:
((1, 2), (3, 4))
- tuple of strings and tuples:
('Hi', (1,2,3), "there")
- tuple of tuple of tuples: ```(((1,2), (3,4)), ((5,6), (7,8)))
# More readable tuple of tuples
nested_tuple = (
(
(1, 2),
(3, 4)
),
(
(5, 6),
(7, 8)
)
)
print(nested_tuple)
Lists¶
- A list is like a tuple but it's mutable (changeable).
- Almost everything you know about tuples also applies to lists.
- Lists are ordered sequences.
- All the sequence operations you learned about with strings and tuples, like
len
, indexing, slicing, loops, thein
operator, etc. apply to lists as well.
Lists are defined inside square brackets, with list elements separated by commas, for example...
['a', 'b', 'c', 1, 2, 3]
Creating Lists¶
# Create an empty list (lists use square brackets instead of parens)
li = []
print('empty list:', li)
# Create and initialize a list with some data
li = ['Python', 123, 3.14, True]
print('non-empty list:', li)
# the same value can occur multiple times in a list
li = ['a', 'a', 'a']
print(li)
List Operations¶
# The len() function gives us the size of a list.
li = ['abc', 123, 3.14, True, 5]
# get the size of a list
list_size = len(li)
print(list_size)
li = ['abc', 123, 3.14, True, 99, 101, "another", "last one - I promise"]
# iterate (loop) over the elements in a list
for i in range(len(li)):
print(li[i])
# A better way to iterate over the elements in a list
li = ['abc', 123, 3.14, True, 99, 101, "another", "last one - I promise"]
for i in li:
print(i)
li = ['abc', 123, 3.14, True]
# test membership in a list
x = 3.14159
if not x in li:
print(x, ‘is not in list’)
else:
print(x, ‘is in list’)
li = ['abc', 123, 3.14, True]
# indexing (list indexes start with zero!)
print(li[2])
# indexing out of bounds raises a runtime error
li = ['abc', 123, 3.14, True]
print(li[99])
li = ['abc', 123, 3.14, True]
# slicing
print(li[1:3])
# concatenating lists
li1 = ['a', 1]
li2 = ['b', 2]
li3 = [99.99]
li4 = li1 + li2 + li3
print(li4)
Lists are Mutable¶
Unlike tuples and strings, we can change the contents of a list after it's created.
# add an element
li = []
print(li)
li.append('elem1')
print(li)
li.append(400)
print(li)
li = ['elem1', 'elem2']
print(li)
# remove an element
li.remove('elem1')
print(li)
# removes only first occurrence of 'element' in list
# differs from del because it’s based on value, not position
li = ['elem1', 'elem2', 3, 99.9]
# remove & retrieve an element based on position
print(‘start:’, li)
for i in range(len(li)):
elem = li.pop()
print(‘removed’, elem, ‘remaining list:’, li)
# If you remove an element that doesn't exist, Python gives a run time error.
# How can that be avoided? Test for existence before removing by value
li = ['elem1', 'elem2']
e = 'elem3'
li.remove(e)
li = ['elem1', 'elem2', 'elem3']
print(li)
# replace an element by index
li[1] = 'foo' # overwrites value at index 2
print(li)
li = ['abc', 123, 3.14, True]
print(li[1:3])
# assign to a list slice
li[1:] = [9,8,7.6]
print(li)
li = ['abc', 123, 3.14, True]
# delete a list element
del li[2]
print(li)
li = ['abc', 123, 3.14, True]
# delete a list slice
del li[1:3]
print(li)
li = ["Maya", "Marc", "Kimba"]
print('original:', li)
# sorting a list
li.sort()
print(‘sorted:’, li)
# sorts in ascending order (small to large) by default
# to sort in descending order, use this syntax:
li.sort(reverse=True)
print(‘reverse sorted:’, li)
li.sort()
print(’re-sorted:’, li)
li = ['abc', 123, 3.14, 'abc', 'abc', True, 'abc', 123]
# get the number of occurrences of a particular value
count = li.count('abc')
print(count)
li = ['abc', 123, 3.14, True, 123]
# get the (first) index of a particular value
index = li.index(True)
print(index)
li = ['abc', 123, 3.14, True, 123]
# reverse a list
li.reverse()
print(li)
li.reverse()
print(li)
Nested Lists¶
Just as we saw tuples of tuples, we can also have also have lists of lists. In fact, we can even have lists of tuples and tuples of lists!
- list of lists:
[[1, 2], [3, 4]]
- list of tuples:
[(1, 2), (3,4)]
- tuple of lists:
([1, 2], [3, 4]])
We can even have lists of lists of tuples of lists of strings... you get the idea, this can get arbitrarily complex. Fortunately, most of the time you only need to use one or two levels, although occasionally you may need to go deeper.
Example¶
Imagine I want to maintain a list of students and their quiz scores. If I think about just one particular student, I might like to store the student's name and each quiz score up to the current lesson. I'd want to use a list because I'm going to want to add quiz results every week and, occasionally, I might need to change a grade.
Here's the data for one student...
student = [ 'Kimba', 95, 100, 90 ]
print(student)
But we have many students, so I need to store one instance of that list for every student. That collection also needs to be mutable, because I may need to add or delete students over time.
This seems like a job for a list of lists, like this one...
students = [
[ 'Kimba', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ],
[ 'Marc', 85, 90 ]
]
print(students)
We can access a sublist, i.e. one of the lists in this list of lists, like this...
students = [
[ 'Marc', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ]
]
student = students[0] # get data for Marc
print(student)
We can access an element of a sublist, like this...
# get Maya's second test score
students = [
[ 'Marc', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ]
]
score = students[1][2]
print(score)
Let's now generate a table of average scores for each student, effectively each student's final grade...
students = [
[ 'Kimba', 95, 100, 90, 98, 95, 99, 93, 97 ],
[ 'Marc', 90, 95, 100, 90, 100, 93, 74 ],
[ 'Maya', 90, 80, 100, 99, 815]
]
#print(students)
for student in students:
total = 0
count = len(student)
for score in student[1:]:
total += score
average = total / (count - 1)
print(student[0], average)
Dictionaries¶
A dictionary is an organized collection of key/value pairs. The data is organized for quick access via the key, somewhat like a real dictionary, where words are the keys and their definitions are the associated values.
Dictionaries are defined using curly braces with key:value pairs separated by colons, like this:
websites = {
'google': 'https://google.com',
'youtube': 'https://youtube.com',
'baidu': 'https://baidu.com',
}
This data type is known by various names in other languages:
- map (C++)
- hashmap (Java)
- associative array (generic term)
This object type is extremely powerful for representing indexed data. The keys in a dictionary are arranged to facilitate fast lookup by key value.
They are optimized for direct, not sequential, access
There is no implied order of keys or values
You can't index a dictionary by position
But you can index dictionaries by key value, as we’ll see
You can't take slices of a dictionary
Dictionaries are mutable, like lists, they can grow, shrink, or change over time
Dictionary keys must be immutable (e.g., string, number, tuple) because changing keys on the fly would confuse the dictionary.
Dictionary values can have any type (mutable or immutable).
Dictionary Operations¶
# Create an empty dictionary (use curly braces instead of parens or square brackets)
grades = {}
print(grades)
# Create and initialize a dictionary
grades = { 'Marc' : 95, 'Maya': 100 }
print(grades)
# If the same key occurs multiple times, python only keeps the last value
x = { 'a' : 1, 'a' : 2 }
print(x)
# but the same value may appear any number of times.
x = { 'a' : 1, 'b' : 1 }
print(x)
# Get the size of a dictionary (returns number of key/value pairs)
grades = { 'Marc' : 95, 'Maya': 100 }
print(len(grades))
# Retrieve the value associated with a given key
grades = { 'Marc' : 95, 'Maya': 100 }
grade = grades['Maya']
print(grade)
# The value inside the square brackets may be a literal, a variable or any
# arbitrary expression. Similar syntax to list/tuple indexing but key based,
# not positional.
grades = { 'Marc' : 95, 'Maya': 100 }
# Attempting to retrieve a non-existent key causes an error
x = 'Marc'
grades[x]
grades = { 'Marc' : 95, 'Maya': 100 }
student = 'foo'
# play it safe by testing for key existence before access
grade = grades[student]
if student in grades:
grade = grades[student]
print('grade for', student, 'is', grade)
else:
print('student', student, 'not found')
# When used with dictionaries, the in operator only checks the existence
# of keys, not values. You can also use “not in” to test for non-existence
# of a key.
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# loop through a dictionary (this iterates over the dictionary keys)
for i in grades:
print(i, grades[i])
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# By default, the keys will appear in random order. You can iterate keys in order by sorting them first:
for i in sorted(grades):
print(i, grades[i])
Dictionaries are Mutable¶
grades = { 'Marc' : 95, 'Maya': 100 }
print('before:', grades)
#Add a key/value pair
grades['Kimba'] = None # new, no grade yet
print('after:', grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': None }
print('before:', grades)
grades['Marc'] = 80 # grade recorded
print('after1:', grades)
grades['Marc'] += 5 # increment Marc's grade
print('after2:', grades)
grades['Kimba'] = 99 # change Kimba's grade
print('after2:', grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# delete a key/value pair
del grades['Marc'] # Marc dropped the course
print(grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# Trying to delete a key that doesn't exist will cause a runtime error.
del grades['Fred']
Nested Dictionaries¶
Just like we can have nested if statements, nested loops, nested tuples, and nested lists, we can also have nested dictionaries.
- dictionary of tuples:
{'key1': (1,2), 'key2': (3,4)}
- dictionary of lists:
{'key1': [1,2], 'key2': [3,4]}
- dictionary of dictionaries:
{
'key1' : {
'key1' : [1, 2],
'key2' : [3, 4]
}
'key2' : {
'key1' : [1, 2],
'key2' : [3, 4]
}
}
This can get arbitrarily complex (dictionaries of lists of tuples of dictionaries of...).
Once again, imagine I want to maintain a list of students and their quiz scores. If I think about just one particular student, I might like to store the student's name and each quiz score up to the current lesson. I need a mutable sequence (i.e. a list) because I'm going to want to add quiz results every week, like this:
student = [ 'Jeff', 95, 100, 90 ]
I'd like to organize the test scores by student name that way I can efficiently find any given student's scores by their name (i.e. by indexing on the dictionary key).
This leads us to a dictionary of lists:
grades = {
'Marc' : [95, 100, 90],
'Maya' : [90, 95, 100]
}
To add a student:
grades[student] = []
To delete a student:
del grades[student]
To add a new score for a student:
grades[student].append(new_score)
To replace the 2nd quiz score for a student:
grades[student][1] = new_score
Rule of thumb for truth value of tuples, lists, and dictionaries¶
All of these objects may be used as boolean values. The rules for converting a tuple, list, or map into a boolean value are as follows:
- if the object is empty, it evaluates to False
- if the object is non-empty, it evaluates to True
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘tuple test…’)
for i in (), (1,2,3), (‘a’, 1), (None,):
print(i, empty(i))
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘list test…’)
for i in [], [1,2,3], [‘a’, 1], [None]:
print(i, empty(i))
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘dictionary test…’)
for i in {}, {1: 2, 3: 4}, {‘a’:1, ‘b’:2, ‘c’:3}, {None:None}:
print(i, empty(i))
Homework¶
Question 1¶
I have a list of things I need to buy from my supermarket of choice.
I want to know what the first thing I need to buy is. However, when I run the program it shows me a different answer than what I was expecting. What is the mistake? Can you fix it in the cell below?
shopping_list = [
"oranges",
"cat food",
"sponge cake",
"long-grain rice",
"cheese board",
]
print(shopping_list[1])
#@title Double click here to reveal solution
print(shopping_list[0]) # because sequence indexes start with zero
Question 2¶
I'm setting up my own market stall to sell chocolates. I need a basic till to check the prices of different chocolates that I sell. I've started the program and included the chocolates and their prices. Finish the program by asking the user to input an item and then output its price.
chocolates = {
'white': 1.50,
'milk': 1.20,
'dark': 1.80,
'vegan': 2.00,
}
# Add your code here.
#@title Double click here to reveal solution
chocolates = {
‘white’: 1.50,
‘milk’: 1.20,
‘dark’: 1.80,
‘vegan’: 2.00,
}
while True:
item = input(“Enter the desired item (q to quit): “).lower()
if item == ‘q’:
break
if item in chocolates:
print(‘The price for {} chocolate is £{:.2f}.\n’.format(item, chocolates[item]))
else:
print(‘Sorry, we don't sell {} chocolate.\n’.format(item))
Question 3¶
Write a function named enumerate()
that takes a list of strings and enumerates them, i.e. it returns a list where each passed string is prefixe by a sequential number, starting at 1. For example...
li = ['test', 'another test', 'last test']
results = enumerate(li)
for i in results:
print(i)
should produce this output:
1. test
2. another test
3. last test
Question 4¶
Write a program that simulates a lottery. The program should have a list of seven numbers that represent a lottery ticket. It should then generate seven random numbers. After comparing the two sets of numbers, the program should output a prize based on the number of matches:
- £20 for three matching numbers
- £40 for four matching numbers
- £100 for five matching numbers
- £10000 for six matching numbers
- £1000000 for seven matching numbers
# Add your code here
#@title Double click here to reveal solution.
import random
my_numbers = random.choices(range(1, 99), k=7)
print(‘your numbers are: ‘, my_numbers)
winning_numbers = random.choices(range(1, 99), k=7)
print(‘winning numbers are: ‘, winning_numbers)
hits = 0
for i in my_numbers:
if i in winning_numbers:
hits += 1 # shorthand for hits = hits + 1
prizes = {
3: 20,
4: 40,
5: 100,
6: 10000,
7: 1000000
}
if hits in prizes:
print(’** You matched {} numbers and won £{}! **’.format(hits, prizes[hits]))
else:
print(‘Sorry, no prize won, better luck next time!’)