Chapter 8

Dictionaries

Subsections of Dictionaries

Introduction

YouTube Video

Resources

Previously, we learned how we can use lists in Python to store multiple values, or elements, in a single variable. Each element in a list is given an index, which allows us to uniquely reference and identify each element’s position in a list. Python lists use consecutive integers starting at $ 0 $ as indexes, which makes for a pretty easy to use data structure.

However, what if we would like to use some other value besides an integer as the index? For example, if we create a data structure to store information about a user account, wouldn’t it be much easier if we could use the username as the index, allowing us to quickly find that particular user’s data?

Thankfully, Python includes another built-in data structure, called a dictionary, that allows us to do exactly that. In a dictionary, instead of indexes we have keys, which can be any data type, that uniquely identify each item, or value, in the dictionary. Other programming languages refer to dictionaries as hash tables, hash maps, or associative arrays, which gives a clue to how they work behind the scenes.

In this lab, we’ll learn how to create and use dictionaries in Python. Dictionaries are a very powerful and useful data structure in Python, with many uses. Let’s see how they work!

Subsections of Introduction

Dictionaries

YouTube Video

Resources

In theory, a dictionary is very similar to a list in Python - it is a data structure that can store many different items within a single variable. However, instead of just storing single values as elements and assigning them sequential indexes, dictionaries store a number of key-value pairs. A key is any value that can be used as a unique identifier for the associated value to be stored in the dictionary.

For example, we can use a dictionary to store the “score” for various words in the game of Scrabble. In this case, our key would be the word stored as a string, such as "test", and the value would be an integer representing the score, 4. These two items make up a key-value pair ("test", 4) that can be stored in a dictionary.

There is one major rule that dictionaries must follow - each key in a dictionary must be unique. Or, put another way, if the dictionary already contains a value for a given key, and we try to add another value that uses the same key, the first value will be overwritten. So, we must be careful and make sure that the keys we choose to use are indeed unique so that they will work in a dictionary.

Creating a Dictionary

Dictionaries in Python can be created in much the same way as a list. We can create an empty dictionary using a set of curly braces {} as shown here:

dict_1 = {}

We can also create a dictionary that contains some initial key-value pairs by placing them inside of the curly braces. Each key-value pair is separated by a comma , with the key provided first, followed by a colon : and then the value. Here’s a brief example:

dict_2 = {"a": 1, "b": 2, "c": 3}

In this dictionary, the keys are the strings "a", "b", "c", and each one is associated with the values 1, 2, and 3, respectively.

Finally, to make it a bit easier to read our code, we can also reformat the code to create a dictionary across multiple lines, with each key-value pair shown on its own line as seen in this example:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

This dictionary associates various animal species with the common name of their classification within the animal kingdom.

Adding Items to a Dictionary

We can also easily add new items to a dictionary by simply providing a key inside of square brackets [] and then using an assignment statement to assign that key a value. Consider this example:

dict_1 = {}
dict_1["Kansas"] = "Topeka"
dict_1["Nebraska"] = "Lincoln"
dict_1["Missouri"] = "Jefferson City"

Here, we start by creating an empty dictionary, and then we can add key-value pairs using the names of states as the key and the state’s capital city as the value.

Square Brackets

Notice that we use square brackets [] to access individual items in a dictionary, just like with lists, but we use curly braces {} to create a dictionary. This can cause confusion for many new Python programmers. So, pay special attention to the syntax of each data structure, and remember that it can sometimes be difficult to tell whether a data structure in Python is a list or a dictionary without reading the code to see where it was created.

Accessing Items in a Dictionary

Once a dictionary contains items, we can access the value associated with a given key by providing that key within square brackets [] after the variable storing the dictionary. Here’s an example of what that looks like in Python:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

print(dict_3["cat"])   # mammal

As we can see, we’re able to provide the key "cat" and then access the associated value "mammal" that was stored in the dictionary.

What if we try to access a key that doesn’t exist? In that case, Python will raise a “KeyError” and the program will crash. So, we’ll have to be careful and make sure we only try to access keys that are actually stored in the dictionary, unless we are using them in an assignment statement to add a new key-value pair.

Updating Items

We can also update the value associated with a given key in a dictionary using an assignment statement, as shown here:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

dict_3["cat"] = "feline"

print(dict_3["cat"])   # feline

In this example, we are simply replacing the value for the key "cat" with a new string “feline”. Remember that the keys in a dictionary must be unique, so if we try to add a new value using the same key, it will simply overwrite the existing value associated with that key.

Dictionaries in Python Tutor

To really understand how dictionaries work in Python, let’s go through a quick example using Python Tutor. Consider the following Python program:

def main():
    base = int(input("Enter a whole number: "))
    powers = {}
    for i in range(6):
        powers[f"{base}^{i}"] = base ** i
    print(powers)


main()

This program will ask the user to provide a number as input, and then it will populate a dictionary where the key is a string representing a mathematical expression including that number, and the value will be the result of that expression. Let’s trace through this program in Python Tutor. As always, you can copy and paste this code in Python Tutor, or click this Python Tutor link to open it on the web.

When we start tracing the program in Python Tutor, we can skip ahead until it reaches the first line of code in the main() function, at which point we’ll see this state:

Tutor 4 Tutor 4

This first line of code asks the user to input a value. For this example, let’s assume the user inputs the string "3". So, we’ll store the integer value $ 3 $ in the base variable as seen here:

Tutor 5 Tutor 5

This line will create an empty dictionary in the powers variable. Just like lists in Python, a dictionary is actually stored in the objects area in memory, and the variable is simply a pointer, or reference, to the location in memory where the dictionary will be stored. So, after we execute that line of code, we should see this state:

Tutor 6 Tutor 6

At this point, we reach the for loop. This loop will iterate $ 6 $ times, so we’ll start by storing the first value $ 0 $ in the iterator variable i and then entering the loop:

Tutor 7 Tutor 7

Inside of the loop, we see a single line of code that is pretty complex. This line will actually perform three different operations! First, it will generate a string using the string format() method, which will become the new key that is added to the dictionary. Then, it will compute the value of the base variable raised to the power of i. Finally, it will store that computed value in the dictionary associated with the newly created key. Once all of those steps are complete, we’ll see a new item in the dictionary as shown here:

Tutor 8 Tutor 8

In Python Tutor, we can see the key for the item on the left, and then the associated value on the right. So, it is similar to how a list is represented, but instead of just showing the index, it will show the key and the value.

At this point, our program will loop back to the start of the for loop. Since there are more iterations to complete, we’ll update the iterator variable i to $ 1 $, and then enter the loop again.

Tutor 9 Tutor 9

Once inside the loop, we’ll add another item to the dictionary, and then loop back to the top.

Tutor 10 Tutor 10

From here, hopefully it is clear what the rest of the program will do. Each time we enter the loop, we’ll generate a new key and value pair to add to the dictionary. So, let’s skip ahead to the end of the loop.

Tutor 19 Tutor 19

At the end of the program, we’ll print the contents of the dictionary to the terminal. Just like with lists, Python will print dictionaries in a clean and easy to understand format, as shown in the output in Python Tutor:

Tutor 20 Tutor 20

A full animation of this program is included below.

Tutor 13 Tutor 13

As we can see, dictionaries in Python work very similar to lists. When we create a dictionary, it is stored in the objects area in memory, and we can easily add, access, and even print the contents of a dictionary in our code. Dictionaries are really one of the most flexible data structures available in Python.

Subsections of Dictionaries

Loops with Dictionaries

YouTube Video

Resources

Another useful way we can work with dictionaries in Python is by iterating through them. In Python 3.6 and beyond, the items in a dictionary will be presented in the same order that they were added to the dictionary. This isn’t always the case in other languages or older versions of Python

Loops with Dictionaries - Keys

There are two ways we can iterate through a dictionary in Python. Below is an example of the first, and simplest, method:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

for key in dict_3:
    print(f"{key}: {dict_3[key]}")

In this method, we are iterating through the entire dictionary using a for loop. However, the dictionary will only store the key for each key-value pair in the iterator variable. So, to access the value associated with the key, we can simply place it in square brackets after the dictionary’s variable name.

When we run this program, we should see the following output:

Output 1 Output 1

As we can see, it will print each key-value pair in the dictionary, one per line.

Loops with Dictionaries - Keys and Values

The other method we can use when looping through a dictionary allows us to access both the keys and values directly within the loop. Here’s an example of using that method:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

for key, value in dict_3.items():
    print(f"{key}: {value}")

In this method, instead of iterating through the dictionary itself, we iterate through the items() in the dictionary. When we call the items() function in a dictionary, we are given a list of the key-value pairs that it contains, and each key-value pair is stored as a tuple. From there, we can access each of the elements in the tuple individually using a comma , between two variable names. It is a bit complex, and touches on a few concepts in Python that we won’t cover in this course, but hopefully it makes sense by looking at the code.

When we run this program, we’ll receive the same output as the previous example:

Output 1 Output 1

While iterating through dictionaries is not as common as iterating through lists, it is still very important to know that it can be done. Hopefully you’ll find this example useful as you continue to work with dictionaries in Python.

Subsections of Loops with Dictionaries

Dictionaries & Functions

YouTube Video

Resources

Finally, let’s briefly look at how dictionaries operate when used as parameters to functions in Python. As we can probably guess from the earlier example in Python Tutor, dictionaries also use call by reference, just like lists.

Here’s a short example program in Python that demonstrates the use of dictionaries with functions.

def add_score(scores):
    name = input("Enter a name: ")
    score = int(input("Enter a score: "))
    scores[name] = score


def average_score(scores):
    total = 0
    for key, value in scores.items():
        total = total + value
    return total / len(scores)


def main():
    scores = {}
    for i in range(4):
        add_score(scores)
    print(average_score(scores))


main()

Let’s take a look at this program using Python Tutor to see how it works. As always, you can copy and paste this code into Python Tutor, or click this Python Tutor link in the lab to open it in a web browser.

First, Python Tutor will iterate through the code and record all of the functions in the objects area. Once it reaches the call to the main() function at the bottom, it will jump to the code inside of that function. At that point, we should see this state:

Tutor 6 Tutor 6

The first thing that this program will do is create an empty dictionary in the scores variable. So, after executing that line of code, we’ll see a new dictionary in the objects area in memory as shown here:

Tutor 7 Tutor 7

Next, we’ll reach a simple for loop that will iterate $ 4 $ times, so we’ll enter that loop:

Tutor 8 Tutor 8

Inside of the loop, we are calling the add_score() function and providing the scores dictionary as an argument. So, when Python Tutor jumps to execute that function’s code, we’ll see it create a new frame for the function and populate that frame with the arguments provided as part of the function call:

Tutor 9 Tutor 9

Since dictionaries in Python also use call by reference, we’ll see that the scores parameter in the add_score() function’s frame is just a reference back to the same empty dictionary that was created earlier. The scores variable in the main() function’s frame also points to the same object in memory.

The add_scores() function will prompt the user to input a name and a score. So, if we assume that the user inputs the string "Name" as the name, and "95" as the score, we should see this state in Python Tutor:

Tutor 12 Tutor 12

The last line of the function will add a new key-value pair to the dictionary, with the name used as the key and the score as the value. So, when the function is ready to return, we should see this state:

Tutor 13 Tutor 13

At this point, the control flow will return back to the main function, so our frame for the add_score() function will be removed. Thankfully, we can see that the new item we added to the dictionary is present in the one dictionary object in memory, which is also referenced from the main() function’s frame. So, once we are back in the main() function, we’ll see this state:

Tutor 14 Tutor 14

As we can see, dictionaries properly follow call by reference, so if we modify a dictionary that is passed to a function as an argument, we don’t have to return it back if we are just working with the same object.

At this point, the program will repeat that process three more times. We’ll skip ahead a bit to the end of the for loop in the main function, which will be this state:

Tutor 36 Tutor 36

Here, our program will call the average_score() function to get the average of all of the scores in the dictionary. So, we’ll jump up to that function and provide the current dictionary as an argument, as shown here:

Tutor 37 Tutor 37

This function is very straightforward. However, let’s skip ahead a bit to when we are inside of the for loop, just to see what that looks like:

Tutor 40 Tutor 40

In our code, we are using the items() function of the dictionary to allow us to iterate over a list of the key-value pairs in the dictionary. So, inside of that for loop, we see both a key and a value variable, and they are populated with the first key and value from within our dictionary.

Once we’ve iterated through the entire dictionary, we’ll be at this state:

Tutor 49 Tutor 49

We’re ready to return from the function, and we’ve computed that our return value will be $ 92.5 $, as seen the frame for the average_score() function. Once we are back in the main() function, we’ll simply print that value to the output:

Tutor 50 Tutor 50

As we’ve seen in this example, working with dictionaries and functions in Python is practically identical to what we saw with lists - both data structures use call by reference when they are passed as arguments to a function. So, as long as we understand how Python is storing the data in memory, we can effectively build programs that operate the way we expect them to.

Subsections of Dictionaries & Functions

Worked Example

YouTube Video

Resources

Finally, let’s go through a complete worked example program that uses dictionaries in Python. Consider the following problem statement:

Write a program that will compute the score of a given word in a game of Scrabble. Assume that there are no special squares present. The program should prompt the user to input a string that only contains letters, and then compute the total score of that string by summing the value assigned to each letter. If the user inputs a string that contains invalid characters, the program should prompt for additional input until a valid word is received.

This is a pretty simple program that can easily make use of a dictionary in Python. So, let’s go through the process of solving it!

Handling Input

As always, we can start our solution by including a main() function and a call to the main() function at the bottom of our code, as shown here:

def main():


main()

Next, we’ll probably want to create a function to handle requesting input from the user. So, we’ll add that function to our solution as well, and we’ll call it from within the main() function itself:

def get_input():



def main():
    word = get_input()


main()

In the get_input() function, we’ll need the while loop structure we’ve already learned to handle input and verify that it is correct, so let’s go ahead and add that:

def get_input():
    word = input("Enter a single word: ")
    while # word is not valid
        print("Error! Invalid Input!")
        word = input("Enter a single word: ")
    return word


def main():
    word = get_input()


main()

The only part we have to figure out is how we can determine if a word contains only valid characters. There are many different approaches we could follow, but the simplest is to use the isalpha() function that is part of the Python string data type. This function, along with many others, are described in the Official Python Documentation.

So, let’s update our code to use that function as the Boolean expression in our while loop:

def get_input():
    word = input("Enter a single word: ")
    while not word.isalpha():
        print("Error! Invalid Input!")
        word = input("Enter a single word: ")
    return word


def main():
    word = get_input()


main()

There we go! that covers the get_input() function, which will prompt the user to input a string that only contains letters.

Computing Score

Next, let’s look at writing a function that will accept a single word as a parameter, and then compute the score of that word according to the official Scrabble scoring rules.

We can start with a simple function definition as shown here:

def compute_score(word):

First, let’s quickly convert the word we are given to lowercase, just in case the user has included any uppercase letters. Remember, we already know that this word should only contain letters, but it may be a mix of uppercase and lowercase. We can use the lower() function to make this conversion:

def compute_score(word):
    word = word.lower()

Next, we’ll need to iterate through each letter in the word. Since we can treat a string just like a list, we can use a for loop for this:

def compute_score(word):
    word = word.lower()
    for letter in word:

We’ll also need some way to keep track of the total score, and then return that value at the end of the function. This is a pretty common pattern in programming known as the accumulator pattern. So, let’s add a total variable to our program, starting it at $ 0 $ and then returning it at the end:

def compute_score(word):
    word = word.lower()
    total = 0
    for letter in word:
        # add letter's score to total
    return total

Finally, we just need some way to convert a letter into a score in Scrabble. Thankfully, we can refer to the Official Scrabble FAQ to find out how many points each letter is worth:

  • (1 point) - A, E, I, O, U, L, N, S, T, R
  • (2 points) - D, G
  • (3 points) - B, C, M, P
  • (4 points) - F, H, V, W, Y
  • (5 points) - K
  • (8 points) - J, X
  • (10 points) - Q, Z

There are many different ways we could build this program, but one simple way would be to assign each letter a value in a dictionary! To make things even easier, we can leave out all of the letters that are worth 1 point, and just include the letters with higher point values. By doing so, if we don’t find our desired letter in the dictionary, we can simply assume that the score must be 1!

So, let’s add that dictionary to our code:

def compute_score(word):
    scores = {"d": 2, "g": 2, "b": 3, "c": 3, "m": 3, "p": 3, 
              "f": 4, "h": 4, "v": 4, "w": 4, "y": 4, "k": 5, 
              "j": 8, "x": 8, "q": 10, "z": 10}
    word = word.lower()
    total = 0
    for letter in word:
        # add letter's score to total
    return total

Now that we have this dictionary available in our code, we can use it inside of the for loop to compute the total score of the word by looking up the value of each letter. However, before we can directly access a letter, we first must determine if a given letter is contained in the dictionary. In Python, we can do this using the in keyword. So, we can add an if statement inside of our for loop that is structured as shown here:

def compute_score(word):
    scores = {"d": 2, "g": 2, "b": 3, "c": 3, "m": 3, "p": 3, 
              "f": 4, "h": 4, "v": 4, "w": 4, "y": 4, "k": 5, 
              "j": 8, "x": 8, "q": 10, "z": 10}
    word = word.lower()
    total = 0
    for letter in word:
        if letter in scores:
            total = total + scores[letter]
        else:
            total = total + 1
    return total

The Boolean expression letter in scores will return true if the current letter in our word is present as a key in the dictionary scores. The same syntax also works for determining if an element is present in a list!

Complete Program

We can complete our program by simply printing the score returned by the compute_score() function in our main() function. So, the complete program is shown below:

def get_input():
    word = input("Enter a single word: ")
    while not word.isalpha():
        print("Error! Invalid Input!")
        word = input("Enter a single word: ")
    return word


def compute_score(word):
    scores = {"d": 2, "g": 2, "b": 3, "c": 3, "m": 3, "p": 3, 
              "f": 4, "h": 4, "v": 4, "w": 4, "y": 4, "k": 5, 
              "j": 8, "x": 8, "q": 10, "z": 10}
    word = word.lower()
    total = 0
    for letter in word:
        if letter in scores:
            total = total + scores[letter]
        else:
            total = total + 1
    return total


def main():
    word = get_input()
    print(compute_score(word))


main()

We can confirm our program works correctly by running it and testing a few different inputs, as shown below:

Output 2 Output 2

This example shows a great way to use dictionaries in our program - we can assign values to various keys, and then easily look them up in the dictionary as needed. Feel free to run this example in Python Tutor on your own to see how it works, and see if you can come up with your own high-scoring Scrabble words.

Subsections of Worked Example

Dictionaries Practice

Let’s try some simple practice problems. These problems are not graded - they are just for you to practice before doing the real exercises in the lab itself. You can find the answers below each question by clicking the button below each question.

8.1 Reading Code

Consider the following Python program:

# Same function as the worked example - only accepts letters
def get_input():
    word = input("Enter a single word: ")
    while not word.isalpha():
        print("Error! Invalid Input!")
        word = input("Enter a single word: ")
    return word


# Notice that this returns a new dictionary
def foo(words):
    output = {}
    for key, value in words.items():
        letter = value[0]
        remain = value[1:]
        for i in range(len(key)):
            new = key[0:i] + letter + key[i:]
            output[new] = remain
        output[key + letter] = remain
    return output


def main():
    word = get_input()
    words = {"": word}
    for i in range(len(word)):
        words = foo(words)
    print(words)


main()

Explain, in your own words, how the the keys of the dictionary that is printed at the end relates to the original word that is provided as input.

A fully correct answer is a succinct description of the output as it relates to the input. A partially correct answer is a step-by-step description of each line in the program and the output it will produce based on the input.

Hint: try using short words, some with unique letters such as “cat” and others with repeated letters such as “abba”. Why does the requirement that dictionary be unique help with this?

8.1 Answer

This program creates all possible permutations of a given set of letters. In simpler terms, it creates all possible unique ways the letters in the word can be rearranged. By using a dictionary, we can prevent duplicate entries since keys must be unique.

8.2 Reading Code

Consider the following Python program:

def main():
    values = {
        "M": 1000, "CM": -100, "D": 500, "CD": -100,
        "C": 100, "XC": -10, "L": 50, "XL": -10,
        "X": 10, "IX": -1, "V": 5, "IV": -1, "I": 1 
    }
    n = input("Enter a string: ")
    out = 0
    for i in range(len(n)):
        if i + 1 < len(n) and n[i:i+2] in values:
            out = out + values[n[i:i+2]]
        elif n[i] in values: 
            out = out + values[n[i]]
        else:
            print("Error!")
    print(out)


main()

Explain, in your own words, how the output that is eventually printed relates to the string that the user inputs.

A fully correct answer is a succinct description of the output as it relates to the input. A partially correct answer is a step-by-step description of each line in the program and the output it will produce based on the input.

Hint: what numerical system uses letters such as M, D, C, L, X, V, and I?

8.2 Answer

This program uses a database of characters and their associated Roman Numeral values. To handle situations where a lower-valued letter is placed before a higher value (which represents subtraction), it first checks the next pair of letters to handle that subtraction, then if that isn’t found it will add the value of the current letter.

8.3 Writing Code

Write a complete Python program that meets the specification below.

The program should first prompt the user to input a single positive integer. If the number provided as input is not positive, the program should print an error and prompt for input again until a valid input is received. Then, the program should use a dictionary to convert the individual digits of the number to their equivalent names, and then print the result to the terminal. The keys in the dictionary should be the individual digits, either as strings or integers, and the values should be strings containing the name of each digit.

For example, if the user provides the following input:

1234

The program should produce the following output:

one two three four

To receive full credit, your program must make use of a dictionary as described above. Your program must also make use of two additional functions in addition to the required main() function.

Be careful about data types! You may find it easier to convert the number back to a string once you’ve determined that it is positive.

8.3 Answer

One possible solution is given below:

def positive_input(n):
    x = int(input(f"Enter a positive integer greater than {n}: "))
    while x <= n:
        print("Invalid input!")
        x = int(input(f"Enter a positive integer greater than {n}: "))
    return x


def convert_digits(n):
    n = str(n)
    digits = {"0": "zero", "1": "one", "2": "two", "3": "three", "4": "four",
              "5": "five", "6": "six", "7": "seven", "8": "eight", "9": "nine"}
    for digit in n:
        print(digits[digit], end=" ")
    print("")


def main():
    convert_digits(positive_input(0))


main()

There are many other valid approaches. Hopefully it is clear that the two additional functions should handle prompting for input and converting the digits, respectively.

Summary

In this lab, we introduced several major important topics in Python. Let’s quickly review them.

Python Dictionaries

Dictionaries in Python allow us to store key-value pairs in a single data structure. Keys are used to uniquely identify an associated value.

Dictionaries can be created using curly brackets {}:

dict_1 = {}
dict_2 = {"a": 1, "b": 2, "c": 3}

Adding and Accessing Dictionary Items

New elements can be added to a dictionary by providing a new key in square brackets:

dict_2 = {"a": 1, "b": 2, "c": 3}
dict_2["d"] = 4

Items can also be accessed and updated using square brackets

dict_2 = {"a": 1, "b": 2, "c": 3}
dict_2["c"] = dict_2["a"] + dict_2["b"]

Loops with Dictionaries

Dictionaries can be iterated by just the keys or by the keys and values in a tuple:

dict_3 = {
    "cat": "mammal",
    "lizard": "reptile",
    "goldfish": "fish",
    "chickadee": "bird"
}

# keys only
for key in dict_3:
    print(f"{key}: {dict_3[key]}")

# keys and values
for key, value in dict_3.items():
    print(f"{key}: {value}")

Functions with Dictionaries

When calling a function that accepts a dictionary as a parameter, the argument is passed using call by reference instead of call by value. The original dictionary can be modified by the function, but it cannot be replaced with a new dictionary unless the reference to that new dictionary is returned from the function.