Skip to content
aviral gupta

// B4.5 · ~35 min · Beginner

Dictionaries and looping techniques

After this lesson you can store and look up values by key, loop over dictionaries and sequences in the usual ways, and build a word counter from dicts and lists.

Lesson 5 of 6 in B4 Data structures

You will be able to

  • Create and change dicts, read values with [] and get(), and test keys with in
  • Loop with items(), enumerate(), zip(), sorted() and reversed()
  • Build dicts with comprehensions and count things with get()
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson B4.4: which of these create a set? Pick all that apply.

    Select all that apply.

  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    stock = {"apple": 3}
    stock["pear"] = 5
    stock["apple"] = 1
    print(stock)
  3. Practice · Activity 3 of 7

    Fill in the default so that the loop counts each word.

    counts = {}
    for word in ["a", "b", "a"]:
        counts[word] = counts.get(word, ____) + 1
    counts[word] = counts.get(word, ) + 1
  4. Practice · Activity 4 of 7

    What does this print?

    print(list(enumerate("ab", start=1)))
  5. Practice · Activity 5 of 7

    With d = {"b": 2, "a": 1}, match each loop helper to the items it produces.

    d = {"b": 2, "a": 1}
  6. Brain teaser · Activity 6 of 7

    Brain teaser. Three names, two scores. What does this print?

    names = ["ana", "ben", "cleo"]
    scores = [3, 5]
    print(dict(zip(names, scores)))
  7. Apply · Activity 7 of 7

    Mini-task. Write invert(phone_book), which takes a dict from names to numbers and returns a new dict from numbers to names. Test it with {"ana": "0151", "ben": "0170"}. Then try two names with the same number, and explain what you see.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A small grade book

A dict of grades, changed, read, looped over and ranked. sorted() with a key ranks the names by grade, and enumerate() numbers the places. zip() pairs two lists into new entries, and a dict comprehension filters the best grades. Change a grade or add a student and run it again.

main.py

grades = {"ana": 91, "ben": 78}
grades["cleo"] = 85  # a new key is added
grades["ben"] = 81  # an existing key gets a new value
print(grades)
print("ben" in grades, "dan" in grades, grades.get("dan", "no grade"))

for name, grade in grades.items():
    print(name, grade)

ranking = sorted(grades, key=lambda name: grades[name], reverse=True)
for place, name in enumerate(ranking, start=1):
    print(place, name)

newcomers = ["eve", "fay"]
marks = [88, 73]
print({name: mark for name, mark in zip(newcomers, marks)})
print({name: grade for name, grade in grades.items() if grade >= 85})

Run it with

python main.py

Output

{'ana': 91, 'ben': 81, 'cleo': 85}
True False no grade
ana 91
ben 81
cleo 85
1 ana
2 cleo
3 ben
{'eve': 88, 'fay': 73}
{'ana': 91, 'cleo': 85}
  • ben keeps his position when his grade changes; only new keys go to the end.
  • grades.get("dan", "no grade") returns the default instead of raising KeyError.
  • The lambda tells sorted() to compare the grades, not the names.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Exercises

Exercise 1 of 3

Clean words

Step 1 of a word counter. words(text) should return the words of text in lower case, with punctuation stripped from both ends, and without pieces that were only punctuation. The starter only splits the text. word.strip(PUNCTUATION) removes those characters from both ends of a word.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Lower-case the whole text first, then split it.

  2. Hint 2

    A list comprehension can strip every word; a second one can drop the empty strings.

  3. Hint 3

    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()], then return [word for word in cleaned if word]

Show a solution

One way to solve it. Yours can look different and still pass the checks.

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()]
    return [word for word in cleaned if word]
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    return text.split()

test_main.py

from main import words


def test_lower_case():
    """Turns every word into lower case"""
    got = words("The Cat")
    assert got == ["the", "cat"], f"words('The Cat') returned {got!r}, expected ['the', 'cat']"


def test_punctuation():
    """Strips punctuation from the ends of words"""
    got = words("Hello, world! Hello?")
    assert got == ["hello", "world", "hello"], f"got {got!r}, expected ['hello', 'world', 'hello']"


def test_no_empty_words():
    """Drops pieces that were only punctuation"""
    got = words("wait ... what")
    assert got == ["wait", "what"], f"words('wait ... what') returned {got!r}, expected ['wait', 'what']"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 3

Count the words

Step 2. count_words(word_list) should return a dict from each word to the number of times it occurs, with the words in the order they first appear. words() from step 1 is already there. Fill in the loop.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Loop over word_list and update counts for each word.

  2. Hint 2

    counts.get(word, 0) is the count so far, or 0 for a new word.

  3. Hint 3

    for word in word_list: counts[word] = counts.get(word, 0) + 1

Show a solution

One way to solve it. Yours can look different and still pass the checks.

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()]
    return [word for word in cleaned if word]


def count_words(word_list: list[str]) -> dict[str, int]:
    """Return how often each word occurs."""
    counts: dict[str, int] = {}
    for word in word_list:
        counts[word] = counts.get(word, 0) + 1
    return counts
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()]
    return [word for word in cleaned if word]


def count_words(word_list: list[str]) -> dict[str, int]:
    """Return how often each word occurs."""
    counts: dict[str, int] = {}
    # Add 1 to the count of each word; a new word starts at 0.
    return counts

test_main.py

from main import count_words, words


def test_counts():
    """Counts each word"""
    got = count_words(["the", "cat", "the"])
    assert got == {"the": 2, "cat": 1}, f"got {got!r}, expected {{'the': 2, 'cat': 1}}"


def test_first_seen_order():
    """Keeps the words in the order they first appear"""
    got = list(count_words(["b", "a", "b"]))
    assert got == ["b", "a"], f"the keys are {got!r}, expected ['b', 'a']"


def test_with_words():
    """Works on the output of words()"""
    got = count_words(words("Go, go, GO!"))
    assert got == {"go": 3}, f"got {got!r}, expected {{'go': 3}}"


def test_empty():
    """No words give an empty dict"""
    got = count_words([])
    assert got == {}, f"count_words([]) returned {got!r}, expected {{}}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 3 of 3

The top words

Step 3. top_words(counts, n) should return the n most frequent (word, count) pairs, highest count first, and words with the same count in alphabetical order. The starter sorts alphabetically only. The program at the bottom prints the top three of a sentence with enumerate().

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Sort counts.items() with a key function that looks at the count.

  2. Hint 2

    A key can return a tuple: Python compares the first part, then the second. A negative count sorts high counts first.

  3. Hint 3

    sorted(counts.items(), key=lambda item: (-item[1], item[0]))[:n]

Show a solution

One way to solve it. Yours can look different and still pass the checks.

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()]
    return [word for word in cleaned if word]


def count_words(word_list: list[str]) -> dict[str, int]:
    """Return how often each word occurs."""
    counts: dict[str, int] = {}
    for word in word_list:
        counts[word] = counts.get(word, 0) + 1
    return counts


def top_words(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
    """Return the n most frequent (word, count) pairs; ties in alphabetical order."""
    ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
    return ranked[:n]


text = "The cat saw the dog. The dog saw a bird!"
for place, (word, count) in enumerate(top_words(count_words(words(text)), 3), start=1):
    print(place, word, count)
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

PUNCTUATION = ".,!?;:"


def words(text: str) -> list[str]:
    """Split text into lower-case words without surrounding punctuation."""
    cleaned = [word.strip(PUNCTUATION) for word in text.lower().split()]
    return [word for word in cleaned if word]


def count_words(word_list: list[str]) -> dict[str, int]:
    """Return how often each word occurs."""
    counts: dict[str, int] = {}
    for word in word_list:
        counts[word] = counts.get(word, 0) + 1
    return counts


def top_words(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
    """Return the n most frequent (word, count) pairs; ties in alphabetical order."""
    return sorted(counts.items())[:n]


text = "The cat saw the dog. The dog saw a bird!"
for place, (word, count) in enumerate(top_words(count_words(words(text)), 3), start=1):
    print(place, word, count)

test_main.py

from main import top_words


def test_most_frequent_first():
    """Puts the most frequent word first"""
    got = top_words({"a": 1, "the": 3, "dog": 2}, 2)
    assert got == [("the", 3), ("dog", 2)], f"got {got!r}, expected [('the', 3), ('dog', 2)]"


def test_ties_alphabetical():
    """Words with the same count come in alphabetical order"""
    got = top_words({"saw": 2, "dog": 2, "the": 3}, 3)
    assert got == [("the", 3), ("dog", 2), ("saw", 2)], f"got {got!r}, expected [('the', 3), ('dog', 2), ('saw', 2)]"


def test_n_larger_than_dict():
    """Returns every pair when n is larger than the number of words"""
    got = top_words({"hi": 1}, 5)
    assert got == [("hi", 1)], f"got {got!r}, expected [('hi', 1)]"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

Reading a missing key with []

stock = {"apple": 3}
print(stock["pear"])

What Python prints

KeyError: 'pear'

Why, and the fix

d[key] raises KeyError when the key is not there. Test first with if "pear" in stock, or use stock.get("pear", 0) to get a default.

Deleting keys while looping over the dict

stock = {"apple": 0, "pear": 5}
for fruit in stock:
    if stock[fruit] == 0:
        del stock[fruit]

What Python prints

RuntimeError: dictionary changed size during iteration

Why, and the fix

Loop over a copy of the keys, for fruit in list(stock), or build a new dict: stock = {k: v for k, v in stock.items() if v != 0}.

Using a list as a key

seats = {["row", 1]: "ana"}

What Python prints

TypeError: cannot use 'list' as a dict key (unhashable type: 'list')

Why, and the fix

Dict keys must be hashable, like set items. Use a tuple: {("row", 1): "ana"}.

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

Keys and values

A dict maps keys to values: ages = {"ana": 30, "ben": 25}. ages["ana"] reads a value, and ages["cleo"] = 41 adds a key or replaces the value of an existing one, because each key appears only once. Reading a missing key with [] raises KeyError; ages.get("dan") returns None instead, and ages.get("dan", 0) returns 0. "ana" in ages tests the keys, not the values. del ages["ben"] removes a key. Keys must be hashable, like set items, and a dict remembers the order in which its keys were added.

Looping techniques

for key in d loops over the keys. for key, value in d.items() gives both at once. enumerate(seq) pairs each item with its position, counting from 0, or from 1 with start=1. zip(a, b) walks two sequences side by side and stops at the shorter one. sorted(seq) loops in sorted order and reversed(seq) backwards; both leave the original alone. Do not add or delete keys in a dict while you loop over it: loop over list(d) instead, or build a new dict.

Dict comprehensions and counting

A dict comprehension builds a dict in one expression: {w: len(w) for w in words}. Add an if to filter: {k: v for k, v in prices.items() if v < 4}. dict(zip(keys, values)) pairs two lists into a dict. The classic counting loop uses get() with a default: counts[w] = counts.get(w, 0) + 1 starts each new word at 0. To rank the result, sort the pairs by their count: sorted(counts.items(), key=lambda item: item[1], reverse=True).

Sources

Last reviewed September 29, 2026