Chapter 5 Containers

Up until this point, we have considered individual variables and the operations that can be applied to them. In this case, each variable is used to hold a single value (such as an integer or a string). But what if we want to go beyond this? What if we want our variables to store multiple things? To motivate why we may want this, let’s consider the following problem:

Write a program to read in 10 integers. Then print them out in the reverse order they were entered

Given what we have covered so far, how would we solve this problem? Well, it seems like we would need to have 10 variables (e.g x1,x2, …, x10), read an integer into each, and then print out the variables in the reverse order that the input took place. This would give us the right answer, but now consider this: what if there were 100 integers? 1000? \(10^9\)?! Clearly this approach will not work in the long run.

In this chapter, we will look at how to overcome this problem (and others) using programming constructs called containers which can be used to hold multiple values at once (lists), and associate values with one another (dictionaries).

5.1 Lists

A list is a type provided by Python that lets us store a collection of values. In Python lists, we can store as many elements as we want, and each element can be of any type (as we will see later, other languages are not as permissive and only allow for the same type).

In Python, we can create an empty list (i.e. a list with no items) as follows:

my_list = list()  # or my_list = []

We can also create a list with a set of items inside it as follows:

another_list = [27, 39, False, "Batman > Superman"]

Some points to note on the above. In both cases, we have created a single variable that is of type list. In other words, we have one variable, but it is being used to refer to many values, and not just the one value we’ve seen previously. In Python, lists are denoted with square brackets, and inside each list is a comma-separated collection of items (or no items if the list is empty).

5.1.1 Modifying Lists

The above examples showed us how to create lists. But we can do much more than that! We can add, remove and modify elements of the list as we wish. One of the most common operations is to add items to a list. In Python, this is done as follows:

student_names = list()  # creates an empty list
student_names.append("Bruce Banner")  # adds string to end of list
student_names.append("Bruce Wayne")  # adds string to end of list
print(student_names) 

Try run the above code and observe the output. Notice that the order of the items in the list is the same as the order in which they were added to the list!

We can also combine a list with another list — this will append the contents of the second list to the end of the first. For example:

student_names = list() 
student_names.append("Bruce Banner") 
student_names.append("Bruce Wayne") 

more_names = ["Nakia", "Okoye", "M'Baku"]
student_names.extend(more_names) # add more_names to list
# Alternative: student_names = student_names + more_names 
print(student_names)

Again, try run the above code and see what the output is. Make sure you understand what is happening here before moving on.


We can remove all the elements from a list using clear() and we can remove a single element by specifying the value to remove, as in the example below

student_names = list() 
student_names.append("Bruce Banner") 
student_names.append("Bruce Wayne") 
student_names.append("Peter Parker")
print(student_names)

student_names.remove("Bruce Wayne")
print(student_names)

student_names.clear()
print(student_names)

Again, try running the above code and observing the output.


Question! What happens if we try to remove an element from a list, but the element does not exist? What happens if we try to remove an element, but there are multiple elements with the same value?

5.1.2 Accessing List Elements

Now that we have a list of items, the next question is how to access the elements from that list. We can access any particular element using its location, or index. An element’s index is its position in the list: the first element is at index \(0\), the second at index \(1\), and so on. If a list has \(N\) elements, then the last element will be at position \(N-1\). An example of this is below:

colours = ['red', 'blue', 'green']
print(colours[0]) # red
print(colours[2]) # green


What if we want to get the last element of the list though? In the above case, we know that the list is of length 3, but in general we might not know the size of the list up front. We can use Python’s len function to compute the length of a given list. For example, len(colours) will return the value \(3\). We can use this to extract the last element in the list, because we know that the last index is len(colours) - 1.

colours = ['red', 'blue', 'green']
print(colours[len(colours) - 1]) # green

Getting the last elements of a list is fairly common, and so Python has provided a convenient way for doing so by using negative indices. If an index is negative, it refers to the elements in the list starting from the back. So an index of \(-1\) is the last element, \(-2\) is the second last element, and so on.

colours = ['red', 'blue', 'green']
print(colours[-1]) # green

5.1.3 Iterating through Lists

We can combine the loops we looked at in the previous chapter with our lists in very powerful ways. The for loop allows us to access every element of the list one at a time and use it for something. The example below shows how to achieve this in Python

sum = 0
numbers = [5, 78, 23]
for x in numbers:
  # x will be each element of list
  sum = sum + x
  print(sum)


In the above example, we directly accessed the value of each element in turn. Alternatively, we could also loop through a list be using each index in turn, as follows:

sum = 0
numbers = [5, 78, 23]
for i in range(len(numbers)):
  sum = sum + numbers[i]
print(sum)


The first approach is shorter and more convenient, but the second approach has the added advantage in that we also have access to the index, in case we need to do something with it. If we wish to have access to both the index and the element at the same time, we can use the enumerate option provided by Python:

names = ["Sansa", "Arya", "Bran", "Rickon"]
for i, name in enumerate(names):
  # enumerate gives us index and value for each element
  print("The name at position", i, "is", name)

5.1.4 Element Exists?

We have seen that we can use the in keyword to loop through a list or range. However, we can also use it to determine whether a particular value is contained in a list! This is achieved by simply writing x in my_list, where x is the value we are searching for, and my_list is the name of the list we want to search. This will return a Boolean with value True if there is at least one occurrent of x in my_list, and False otherwise. An example of this is below

concert_lineup = ["Cassper", "AKA", "Kwesta"]
if 'AKA' in concert_lineup:
  print("I'm not going")

Question! In the above use case, we could use in to determine whether the list contains a particular value. But what if we wanted to know the index of a particular position? Find out how to do this in Python? What is the resulting index if the value is not contained in the list?

Question! In one of the twenty seven thousand, three hundred and seventy two Marvel films, Thanos believes that overpopulation will destroy the universe. He has managed to collect all the Infinity Stones (oh noes! 🥺), and is about to use their power to remove half of everyone from existence!

Thanos adding a marble to his gold hand thingy

Figure 5.1: Thanos adding a marble to his gold hand thingy

Write a program that reads a series of names into a list. I suggest using your own name and your friends’! Loop through each name in the list and flip a coin. If heads, the person survives. If tails, the person does not! Print out all the people that survived Thanos’ plan! Did you? (Hint: You will need to look up how to generate a random number/Boolean)

5.2 Dictionaries

Lists allow us to store a collection of values. But what if we want to associate one value with another? For example, we may wish to associate a student number with a student’s name. How would we maintain this link? These associative containers are said to be a collection of key-value pairs, where each key is associated with a particular value (in our example, a key would be a student number and the value would be the name attached to that number).

These structures have many names depending on the programming language (maps, dictionaries, symbol tables, etc), but they all offer the ability to

  1. add a key-value pair to the collection,
  2. remove a key-value pair
  3. modify an existing pair
  4. lookup a value given a key.

In Python, we use a dictionary, denoted by the type dict. Let’s look at how to use dictionaries by considering the case of associating student names with their marks. The names and associated marks are:

Vegeta: 99
Goku: 9001
Freeza: 80

In Python, we can create an empty dictionary (i.e. a dictionary with no items) as follows:

my_dict = dict() # or my_dict = {}

We can also create a dictionary with a set of items inside it as follows:

another_dict = {"Vegeta": 99, "Goku": 9001, "Freeza": 80}

In the above example, the key is the string/name, and the associated value is the integer; however, the key can be many other data types as well. One very important property is that dictionaries only allow for unique keys. It is forbidden to have two entries in a dictionary with the same key.

5.2.1 Adding/Modifying

Let’s consider the following example below and walk through how entries are added an modified in a dictionary

marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
marks["Vegeta"] = 40 # overwrites

5.2.2 Removing

Let’s consider the following example below and walk through how entries are added an modified in a dictionary

marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
marks["Vegeta"] = 40
del marks["Vegeta]


5.2.3 Querying

The main use of a dictionary is that we can look up an associated value given a key quickly and efficiently. The example below illustrates this

marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
is_goku_a_key = "Goku" in marks
goku_mark = marks["Goku"]


5.2.4 Iterating

If we wish to loop through every key-value pair in the dictionary, we can do so by looping through the dictionary’s items() as follows:

marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
for name, mark in marks.items():
  print(name, ":", mark)
"""
Output:
Vegeta: 99
Goku: 9001
"""

It is very important to note that in certain languages, ordering is not guaranteed by dictionaries. For example, if I insert two entries one after the other, there is no guarantee that they will be printed out in that same order. If you are using Python with version 3.6 or greater, dictionaries will preserve the order in which entries were added to the dictionary. For earlier versions of Python, there is no guarantee!

5.3 Sets

A related structure is a set, which stores keys only. Unlike dictionaries, we cannot associate keys with values, but we can use sets to store a collection of data. Sets are therefore very similar to lists, but with a few major differences:

  1. Each element in the set is unique. If a duplicate value is added to the set, it will be ignored.
  2. The entries in the set are not ordered.
  3. Once a key has been added to a set, it cannot be modified. Sets only support adding and removing elements.
  4. Sets do not support indexing. Because there is no order to the elements, we cannot ask what the \(n\)th element is.

In Python, we can create an empty set (i.e. a set with no items) as follows:

my_dict = set()

We can also create a dictionary with a set of items inside it as follows:

another_set = {"Vegeta", "Goku", "Freeza"}

5.3.1 Adding

Let’s consider the following example to see how elements are added to a set

names = set()
names.add("Vegeta")
names.add("Goku")

5.3.2 Removing

Let’s consider the following example to see how elements are removed from a set

names = set()
names.add("Vegeta")
names.add("Goku")
names.remove("Vegeta")

5.3.3 Querying

The main use of a set is that we can determine if something is already in the set. This is identical to how we would check a list, but is significantly faster!

names = set()
names.add("Vegeta")
names.add("Goku")
is_goku_a_key = "Goku" in names


5.3.4 Iterating

We can loop through a set just as we would through a list. Remember that sets do not support indexing, and so we would accomplish this as follows: If we wish to loop through every key-value pair in the dictionary, we can do so by looping through the dictionary’s items() as follows:

names = set()
names.add("Vegeta")
names.add("Goku")
for name in names:
  print(name)
"""
Output:
Vegeta
Goku
"""

Also remember that sets have no ordering, so the order that elements are printed out are not guaranteed to be the same as the order they were added.