On this page

Dictionaries and Sets

A list answers one question well: what is at position 3? Many programs need a different question answered: what is the price of tea, how many times have I seen this word, has this person already voted? You can answer those with a list and a loop, but Python has two containers built for them. A dictionary finds a value by a key you choose. A set knows whether it holds something. Between them they replace a great many loops.

Before this, you can: build and change a list; loop over a string or a list; count the turns that pass a test.

By the end you will be able to look a value up by its key, add and change entries and deal with a key that is missing, loop over a dictionary, count things with one, use a set to ask "have I seen this?", and choose between a list, a dictionary and a set by the question you need answered.

Before you read on, decide what this does:

counts = {}
for word in ["the", "cat"]:
    counts[word] = counts[word] + 1
  File "your_program.py", line 3
    counts[word] = counts[word] + 1
KeyError: 'the'

It stops on the first word. counts[word] + 1 has to read the count for "the" before it can add to it, and there is no entry for "the" yet. Reading a key that is not there is an error, a KeyError, and the message names the key. Every counting program meets this, on the first new thing it sees. The lesson shows the two usual ways round it.

Looking up by name

A dictionary holds entries. Each entry is a key and the value that goes with it, written with a colon between them, inside curly brackets:

prices = {"tea": 2, "cake": 4}
print(prices["cake"])
print(len(prices))
4
2

prices["cake"] looks like indexing, and it is the same idea: something in square brackets picks out a value. But what goes in the brackets is a key, not a position. A dictionary has no position 0:

prices = {"tea": 2, "cake": 4}
print(prices[0])
  File "your_program.py", line 2
    print(prices[0])
KeyError: 0

Python looked for an entry whose key is the number 0 and found none. Keys are usually text or numbers, and a pair such as (2, 0) can be a key too. A list cannot.

Finding a value by its key is usually fast even in a large dictionary, because Python does not search through the entries one by one.

New keys, changed values, missing keys

Assigning to a key adds an entry if the key is new, and replaces the value if the key is already there. There is no separate "add":

prices = {"tea": 2}
prices["cake"] = 4
prices["tea"] = 3
print(prices)
{'tea': 3, 'cake': 4}

A dictionary can change, like a list, and everything in References and Grids applies: two names can refer to one dictionary, and a function can change the one it is given. When you want a second dictionary, ask for one, exactly as with a list: copy makes a new dictionary with the same entries. Adding an entry to the copy, or giving one of its keys a new value, leaves the original's entries as they were. The values themselves are shared, as the rows of a grid were: if a value is a list, both dictionaries refer to that one list.

stock = {"ball": 3, "net": 1}
left = stock.copy()
left["ball"] = 0
print(stock["ball"], left["ball"])
3 0

Reading is where care is needed, because a missing key is an error. There are two ways to avoid it. in asks whether a key is there. get reads a key and hands back a value of your choosing when it is missing:

prices = {"tea": 2}
print("milk" in prices)
print(prices.get("milk", 0))
print(prices.get("tea", 0))
print(len(prices))
False
0
2
1

in on a dictionary asks about keys, not values. And get only reads: after prices.get("milk", 0) there is still no entry for milk, as the last line shows.

Every key and its value

A for loop over a dictionary visits its entries in the order they were added. items() hands out each entry as a pair, which the loop takes apart into two names:

prices = {"tea": 2, "cake": 4}
for item, price in prices.items():
    print(item, "costs", price)
tea costs 2
cake costs 4

This is the pair from the last module at work: each entry arrives as (key, value), and item, price takes it apart.

from scimigo import canvas, text

canvas(600, 400)
prices = {"tea": 2, "cake": 4, "soup": 5}
y = 60
for item, price in prices.items():
    text(60, y, item)
    text(300, y, price)
    y = y + 40
tea2cake4soup5

Looping over the dictionary itself, for item in prices, gives the keys only.

Counting things

Counting how often each thing appears is the job a dictionary is best known for. The key is the thing, and the value is how many times it has been seen:

counts = {}
for letter in "banana":
    counts[letter] = counts.get(letter, 0) + 1
print(counts)
{'b': 1, 'a': 3, 'n': 2}

The line inside the loop is worth reading slowly. counts.get(letter, 0) is the count so far, or 0 if this letter is new. One is added, and the result is stored under the letter, which makes the entry the first time and replaces it after that. This is the first way round the error at the top of the page.

The second way spells the two cases out, with in:

counts = {}
for letter in "banana":
    if letter in counts:
        counts[letter] = counts[letter] + 1
    else:
        counts[letter] = 1
print(counts["a"])
3

Both are fine. The get version is shorter, and is the one you will see most.

Seen it before?

A set holds values, each one once, with no keys and no positions. set(...) makes one from a list, and curly brackets with no colons write one out:

visitors = ["ana", "bo", "ana", "cy", "bo"]
seen = set(visitors)
print(len(seen))
print("cy" in seen)
seen.add("ana")
seen.add("dee")
print(len(seen))
3
True
4

Five names went in and three are held, because repeats are kept once. Adding "ana" again changes nothing. A set has no order you can rely on, so there is no seen[0].

What a set is for is the question in. Asking whether a value is in a list means looking through the list; a set can usually answer without looking at every value it holds. So the usual pattern is to remember what you have met in a set and consult it as you go:

names = ["ana", "bo", "ana", "cy", "bo"]
seen = set()
first_visits = []
for name in names:
    if name not in seen:
        first_visits.append(name)
        seen.add(name)
print(first_visits)
['ana', 'bo', 'cy']

The list keeps the order; the set answers "have I seen this?". They are often used together like this.

Which one do I need?

Choose a container by the question you will ask of it most.

You need to ask Use Because
what is at position 3? what comes next? a list it keeps order, and allows repeats
what goes with this name? a dictionary it finds a value by its key
have I seen this? is it one of these? a set it answers in without searching, and holds each value once

Scores in the order they were recorded: a list. The stock of each item in a shop: a dictionary, from item to number. The people who have already voted: a set. A wrong choice still works, and costs you a loop every time you ask.

In the lab

You will look values up, add and change entries, see what a missing key does and avoid it in two ways, draw a menu from a dictionary, count the letters of a word, use a set to ask what has been seen, choose a container for five small jobs, and then write functions that count words and find the first letter that appears only once.

Your turn

Now try it yourself

Predict what a program will do, run it, and step through it line by line. The checks tell you when your own code works.

Loading the lab…

In this lab

  1. Look it up

    A dictionary finds a value by its key. Decide what each line of this program does. One of them fails.

  2. Add, change, missing

    Assigning to a key adds an entry or replaces a value. Reading a key that is not there is an error, and there are two ways to ask first. Follow the dictionary through this program. The last line fails.

  3. Menu

    A café's menu is a dictionary from item to price. The program writes one line of it. Change it so that it writes every item and its price, one row for each, whatever is on the menu.

    • Row number i is at y = 60 + 40 × i, in the order of the dictionary.
    • The item's name is written at x = 60 and its price at x = 300.

    The checks try other menus.

  4. Counting

    This is the counting loop. On each turn it reads the count so far for one letter, or 0 if the letter is new, adds one, and stores the result. Follow counts through the six letters of banana.

  5. Count the letters

    This program is meant to count how often each letter of word appears, and then print each letter with its count. It prints 1 for every letter.

    Change the loop so that each time a letter appears, one is added to its count. For banana the output is b 1, a 3, n 2. Leave the loop that prints as it is.

  6. Seen it before?

    A set holds each value once and answers the question "is it in?". Decide what each line prints.

  7. Which one do I need?

    Here are a list, a dictionary and a set, each used for the question it answers best. Decide what the program prints, then choose a container for each of four small jobs.

  8. Word chart

    Write word_counts(sentence). It returns a dictionary from each word of the sentence to the number of times it appears. Before counting, clean the text: make it lower case, and remove every full stop and comma. The function prints nothing.

    Then draw a chart of sentence, using your function. For entry number i of the dictionary, in its own order:

    • a "seagreen" bar 70 wide at x = 40 + 90 × i, 30 tall for each time the word appears, with its bottom edge at y = 300;
    • the word written at x = 40 + 90 × i, y = 330.
  9. First letter that appears once

    Write first_unique(text). It returns the first character of text that appears exactly once in it. If every character is repeated, or the text is empty, it returns an empty string. The function prints nothing.

    "First" means first in the text. In swiss the answer is w: s is repeated, and w comes before i.

  10. Equipment checkout

    A club lends equipment from limited stock. Write checkout(stock, requests).

    • stock is a dictionary from item to the number available, such as {"ball": 3, "net": 1}.
    • requests is a list of pairs, each an item and a number wanted, such as ("ball", 2).

    Go through the requests in order. If the item is not stocked at all, ignore the request. If there are enough, lend them: the number available goes down. If there are not enough, refuse: nothing is lent, and the item's name is added to a list of refusals.

    Return a pair: a new dictionary of what is left, and the list of refusals. The stock you were given must be unchanged.

  11. Came both days

    A museum has a list of visitor names for each of two days. Names can repeat within a day. Write both_days(day_one, day_two), which returns a list of the names that appear on both days: each name once, in the order they first arrived on day one.

    The function prints nothing and changes neither list.

  12. The first new word crashes

    This word counter stops with an error before it has counted anything. Run it and read the message: it names a key.

    Say to yourself which word that is, and why that word. Then fix the one line so that the program counts every word, and prints how often the appears and how many different words there are: for the sentence given, 2 4.

  13. Choose and defend

    Three jobs. For each, write a comment naming the container you would use (a list, a dictionary or a set) and the one question it makes easy. Then write the program for one of them.

    1. A library records which books are out on loan, and needs to answer "is this book out?" many times a day.
    2. A teacher records the mark each pupil got in a test, and needs to look a pupil's mark up by name.
    3. A weather station records the temperature every hour, and needs to draw the last 24 readings in order.

    There are no checks. Go through the list below when you are done.