8  Dictionaries and Sets

In This Chapter

Lists and Tuples are fantastic for storing items in a specific, numbered order. But what if you don’t care about the order of the data? What if you want to look up a piece of information using a name instead of a number?

If you want to find the player’s health in a list, you have to memorize that health is stored at Index 2. That is confusing and leads to bugs. It would be much easier if we could just ask the computer: “Hey, what is the ‘health’?”

In this chapter, we will learn how to two new data storage types. We will cover:

  • Dictionaries: Storing data in Key-Value pairs.
  • Dictionary Methods: Using .get(), .keys(), .values(), and .items().
  • Iterating: How to cleanly loop through a dictionary.
  • Inverting a Dictionary: A classic computer science algorithm.
  • Sets: Unordered collections of unique items.
  • Comparing Collections: How to choose between a List, Tuple, Set, or Dict.
  • The Project: Building a Secret Cipher / Decoder Ring.

8.1 Dictionaries: Key-Value Pairs

A Dictionary (or dict for short) is a data structure that stores information in Key-Value pairs.

Think of a real-world language dictionary. You don’t read it front-to-back, and you don’t ask for “word number 4,512.” You look up a specific word (the Key) to find its definition (the Value).

In Python, we create dictionaries using curly braces { }. We separate the Key and the Value using a colon (:), and we separate each pair using a comma.

# A dictionary holding the ages of several people
ages = {"Dave": 20, "Bob": 28, "Jehosophat": 17}

# To make it more clear programmers often write it like this:
ages = {
    "Dave": 20,
    "Bob": 28,
    "Jehosophat": 17
}

Accessing and Modifying Data

To get a value out of a dictionary, you use square brackets [ ], just like a list. But instead of passing an index number, you pass the exact Key!

print(ages["Bob"])
# Output: 28

Dictionaries are mutable or changeable. You can easily change a value, or add a brand new Key-Value pair, using the assignment operator (=):

ages = {"Dave": 20, "Bob": 28, "Jehosophat": 17}

# Overwriting an existing value
ages["Dave"] = 21

# You can overwrite like this also
ages["Bob"] += 1

# Adding a completely new Key-Value pair!
ages["Kevin"] = 45

print(ages)
# Output: {'Dave': 21, 'Bob': 29, 'Jehosophat': 17, 'Kevin': 45}

The KeyError Pitfall

If you ask a dictionary for a Key that does not exist, the computer will crash with a KeyError.

ages = {'Dave': 21, 'Bob': 29, 'Jehosophat': 17, 'Kevin': 45}
print(ages["George"])
# Crash! KeyError: 'George'

To avoid this, we can use the built-in .get() method. The .get() method asks for a Key, but it also allows you to provide a safe “default” value to return if the Key is missing. It completely prevents the crash!

ages = {'Dave': 21, 'Bob': 29, 'Jehosophat': 17, 'Kevin': 45}

# .get(Key, Default_Value)
age = ages.get("George", 0)

print(f"George's age is {age}")
# Output: George's age is 0

age = ages.get("Kevin", 0)

print(f"Kevin's age is {age}")
# Output: Kevin's age is 45

8.2 Dictionary Rules & Memory

Because dictionaries use a special memory trick called hashing to make looking up Keys incredibly fast, there are two strict rules you must follow:

  1. Keys must be unique. You could not have two “Dave” keys in the same dictionary. If you try to add a second one, it will just overwrite the first one. Values, however, do not have to be unique ("Dave" and "Bob" could both be 30).

  2. Keys must be Immutable. You can use Strings, Integers, or Tuples as Keys because they are locked and unchangeable. You cannot use a List as a Key. If the list changed in memory, the dictionary would lose track of where the data was stored!

Just like lists, dictionaries suffer from the Aliasing trap. If you type dict_b = dict_a, both name tags point to the exact same dictionary in memory. Changing one changes the other!

8.3 Iterating Through Dictionaries

Earlier, we learned how to use a for loop to travel through a list. We can also iterate through dictionaries, but because they have two parts (Keys and Values), Python gives us three different ways to loop through them.

Let’s use an English-to-Spanish translation dictionary as our example:

translations = {
    "hello": "hola",
    "goodbye": "adios",
    "cat": "gato"
}

1. Looping through Keys: (.keys()) By default, if you run a for loop on a dict, it only loops through the Keys.

for english_word in translations.keys():
    print(english_word)
# Output: hello, goodbye, cat

2. Looping through Values: (.values()) If you only care about the values, use the .values() method.

for spanish_word in translations.values():
    print(spanish_word)
# Output: hola, adios, gato

3. Looping through Both: (.items()) This is the most powerful and common way to loop through a dictionary. The .items() method hands you both the Key and the Value at the exact same time. You just need to provide two temporary variables in your for loop!

# We use 'eng' for the Key, and 'span' for the Value
for eng, span in translations.items():
    print(f"The word '{eng}' in Spanish is '{span}'.")
# Output:
# The word 'hello' in Spanish is 'hola'.
# The word 'goodbye' in Spanish is 'adios'.
# The word 'cat' in Spanish is 'gato'.

Advanced Manipulation: Inverting a Dictionary

What if we have an English-to-Spanish dictionary, but suddenly we need to translate Spanish-to-English? We don’t need to manually type out a whole new dictionary; we can write an algorithm to invert it!

We can create an empty dictionary, loop through the original .items(), and assign the old Values as the new Keys!

translations = {"hello": "hola", "goodbye": "adios", "cat": "gato"}
spanish_to_english = {}

for eng, span in translations.items():
    # We assign the new dictionary using the flipped pair!
    spanish_to_english[span] = eng

print(spanish_to_english)
# Output: {'hola': 'hello', 'adios': 'goodbye', 'gato': 'cat'}

8.4 Sets: The Unique Pile

There is one more collection type we need to learn: the Set.

A Set looks like a dictionary because it uses curly braces { }, but it does not have Key-Value pairs. It is just a pile of single items.

ingredients = {"flour", "sugar", "milk", "eggs"}

Sets have two massive differences from Lists and Tuples:

  1. They are Unordered. A Set has no indexes. There is no ingredients[0]. The computer just throws them in a pile.

  2. They must be Unique. A Set absolutely cannot contain duplicates.

Because Sets cannot contain duplicates, they are a great tool for filtering data and are heavily used in mathematics. If you have a messy list with a hundred duplicate words, you can cast it into a Set to instantly delete all the duplicates!

loot = ["gold", "sword", "gold", "gold", "gem", "sword"]

# Instantly destroy duplicates by casting the List into a Set!
unique_loot = set(loot)

print(unique_loot)
# Output: {'gem', 'sword', 'gold'}

8.5 Set Math (Unions and Intersections)

Sets also allow us to do incredibly fast comparisons using mathematical operators.

party_a = {"Arthur", "Lancelot", "Gawain"}
party_b = {"Gawain", "Percival", "Arthur"}

# INTERSECTION (&): Who is in BOTH parties?
print(party_a & party_b)
# Output: {'Arthur', 'Gawain'}

# DIFFERENCE (-): Who is in Party A, but NOT in Party B?
print(party_a - party_b)
# Output: {'Lancelot'}

Advanced use of sets is something you will want to learn if you use Python for mathematical proofs and calculations.

8.6 Comparing Collections (The Cheat Sheet)

You now know the four major Data Structures in Python. Knowing when to use each one is what what will make you a more effecient programmer.

  • List [ ]: Use when you have a collection of items, the order matters, and you need to add, remove, or sort them.
  • Tuple ( ): Use when you have an ordered collection of items that should never change.
  • Dictionary {k: v}: Use when you want to look up data using labels (keys) instead of numbers.
  • Set { }: Use when you have a pile of items, you don’t care about the order, and you need to ensure there are absolutely no duplicates.

The Project: The Cipher Decoder Ring

We are going to build a cryptography program. We will use a dictionary to map letters of the alphabet to secret symbols.

We will write a function that takes a normal string, loops through every single letter, looks up the secret symbol in our dictionary, and builds an encrypted message!

Type this code into a new file, save it as cipher.py, and run it.

# Setup the dictionary mapping
cipher = {
    "a": "@",
    "e": "#",
    "i": "!",
    "o": "*",
    "u": "%",
    "s": "$",
    "t": "+"
}

# Get the users input
print("--- THE SECRET DECODER ---")
message = input("Enter a message to encrypt: ").lower()

# Encrypt the text by looping through and replacing from dictionary
encrypted_message = ""
            
# We can use a 'for' loop to travel through a string, character by character!
for letter in message:
    # We use .get() so if they type a space or punctuation, it just leaves it alone!
    secret_symbol = cipher.get(letter, letter)
                
    # Add the new symbol onto our result string
    encrypted_message += secret_symbol

encrypted_message

print(f"\nEncrypted Message: {encrypted_message}")

Run it and type "We are under attack". The computer will spit out: w# @r# %nd#r @++@ck.

Challenges

Ready to test your knowledge? Try these challenges on your own.

Challenge 1: The Decryptor

You have a program that encrypts messages, but how do you read them? Modify your cipher.py script. Before you ask the user for input, write a for loop that iterates through cipher.items() to automatically build a new, inverted dictionary called decryption_key.

Challenge 2: The Missing Key

A fellow programmer wrote this code to print a monster’s weakness, but it crashes if the monster isn’t in the database. Rewrite the print statement using the .get() method so that if the monster is missing, it safely prints “Weakness unknown.”

# Broken Code!
monster_weaknesses = {"goblin": "fire", "troll": "acid", "yeti": "ice"}

enemy = input("What monster are you fighting? ")
print(f"Use {monster_weaknesses[enemy]} against the {enemy}!")

Challenge 3: The Frequency Counter (Advanced)

This is a classic computer science interview question! Write a script that asks the user to type a sentence. Create an empty dictionary called letter_counts. Use a for loop to look at every letter in the sentence. If the letter is not in the dictionary, add it with a value of 1. If it is in the dictionary, update its value by adding 1.

At the end, print the dictionary to see exactly how many times each letter was used in the sentence!