Day 9 / 25 Dictionaries & Sets 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 9

Week 2 · Functions & Data Structures · Day 9

Dictionaries & Sets

Key-value modelling, membership testing that stays fast as data grows, and a framework for choosing the right structure.

Study time
4 hours
Reading
Think Python, Ch. 11 & 13
Focus
key-value · hashing · choosing

By the end of today you can

  1. Build and read a dictionary, and reach for .get() when a key may be missing
  2. Count and group with a dictionary — the two patterns you will use most
  3. Explain hashing well enough to predict what can and cannot be a key
  4. Say why in on a dict stays fast while in on a list does not
  5. Use a set for membership, deduplication and set arithmetic
  6. Choose between list, dict and set from the question being asked
  7. Iterate a dict correctly with .items()
  8. Recognise when nesting dictionaries has become a class waiting to happen

Today's videos

Watch each video, then work the matching sections below. Watching alone will not do it.

9A
Dictionaries: Counting & Grouping (120 min)
Keys instead of positions · .get() and when a KeyError is the right answer · the counting pattern · the grouping pattern · iterating with .items() · Counter and defaultdict, after you have written them by hand.
9B
Hashing, Sets & Choosing (120 min)
Hashing explained without the maths · why dict lookup does not slow down · which types can be keys, and why · sets for membership, deduplication and arithmetic · a decision table for list vs dict vs set.

1Key-value modelling

When position is the wrong way to find things.

A list answers "what is at position 3?". A dictionary answers "what is associated with this key?" — which is what you actually want far more often.

dicts.py
ages = {"Amin": 34, "Ada": 36, "Grace": 45}

print(ages["Amin"])
ages["Alan"] = 41            # add
ages["Amin"] = 35            # update — same syntax

print("Ada" in ages)
print(len(ages))
print(ages.get("Nobody", 0))  # a default instead of a crash
34 True 4 0
The KeyError, and the two ways past it

ages["Nobody"] raises KeyError. Use .get(key, default) when a missing key is normal, and let the KeyError happen when a missing key means something has genuinely gone wrong. Silencing every error is not robustness — it is hiding.

Iterating properly

iterating.py
for name in ages:                 # keys, by default
    print(name)

for name, age in ages.items():    # both — use this one
    print(f"{name} is {age}")

.items() gives you a tuple per entry, which you unpack exactly as you learned yesterday. Since Python 3.7 dictionaries keep insertion order, so iteration is predictable — but do not confuse that with being sorted.

2Counting and grouping

Two patterns that cover most real dictionary work.

counts.get(ch, 0) + 1 is the whole counting pattern. The .get supplies zero the first time a key is seen, so there is no special case for "first time".

Grouping

grouping.py
words = ["apple", "avocado", "banana", "blueberry", "cherry"]
by_letter = {}

for word in words:
    first = word[0]
    if first not in by_letter:
        by_letter[first] = []
    by_letter[first].append(word)

for letter, group in by_letter.items():
    print(letter, group)
a ['apple', 'avocado'] b ['banana', 'blueberry'] c ['cherry']
The standard library has both of these

collections.Counter does the counting pattern and collections.defaultdict(list) does the grouping one. Write them by hand today so you know what they do, then use the built-ins forever after. You meet them properly tomorrow, when modules arrive.

3Hashing, intuitively

Why a dictionary does not slow down as it grows.

Finding a name in a list means checking items one at a time — a thousand items, up to a thousand comparisons. A dictionary does something different: it computes where the key should live.

A hash function turns a key into a number; the number picks a bucket. Lookup runs the same function and goes straight to that bucket — it does not scan. That is why a dictionary with a million entries is about as fast as one with ten.

Which explains the rules about keys

Can it be a key?TypeWhy
Yesstr, int, float, bool, tupleImmutable — the hash never changes
Nolist, dict, setMutable — the hash would change and the key would be lost
Now the tuple/list distinction pays off

Yesterday a tuple looked like a list with a restriction. Today the restriction is the feature: because a tuple cannot change, its hash is stable, so it can be a key. locations[(3, 4)] = "treasure" works; a list there raises TypeError: unhashable type: 'list'.

The cost, measured

x in some_list is the blue line — the work grows with the data. x in some_dict is the green line — flat. For a handful of items the difference is irrelevant. For fifty thousand it is the difference between instant and unusable. You will make this argument properly with Big-O on Day 11.

4Sets

A dictionary with the values thrown away — which is more useful than it sounds.

sets.py
seen = {3, 1, 4, 1, 5, 9, 2, 6, 5}
print(seen)                      # duplicates gone, order not guaranteed

tags = set()                     # NOT {} — that is an empty dict
tags.add("python")
tags.add("python")               # no effect
print(len(tags))

a = {1, 2, 3, 4}
b = {3, 4, 5}
print(a & b)                     # in both
print(a | b)                     # in either
print(a - b)                     # in a only
print(a ^ b)                     # in exactly one
{1, 2, 3, 4, 5, 6, 9} 1 {3, 4} {1, 2, 3, 4, 5} {1, 2} {1, 2, 5}
{} is an empty dict, not an empty set

This catches everyone once. Use set() for an empty set. There is no shorter spelling, because {} was taken by dictionaries first.

The three things sets are for

  1. Deduplicationlist(set(items)) removes duplicates in one step (and loses order).
  2. Membershipif word in stopwords: where stopwords is a set, not a list.
  3. Set arithmetic — "which users are in both groups?" is a & b, not a nested loop.

5Choosing the structure

The question you are asking picks the answer.

If the question is…Reach forBecause
"What is at position n?"listOrder and position are the point
"What is associated with k?"dictDirect lookup by key
"Have I seen x before?"setFast membership, no duplicates
"How many of each?"dictThe counting pattern
"Which are in both?"seta & b
"One record, several fields"tupleFixed shape, immutable, hashable

When a dict has outgrown itself

outgrown.py
# this works, and it is a warning sign
book = {
    "title": "Think Python",
    "author": "Downey",
    "year": 2015,
    "tags": {"python", "beginner"},
}

# every access is a string you can typo, with no help from the editor
print(book["athor"])     # KeyError, at runtime, maybe in production
This is Day 12's argument, planted early

A dictionary with a fixed set of keys, copied around your program, is a class waiting to happen. When every record has the same shape and you keep writing functions that take that dict as their first argument, you have discovered objects without naming them. Day 12 names them.

>_Python playground

A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 10 — Files, Modules & Mini-Project 2

Programs that outlive their own run: files, modules, and Mini-Project 2.

Continue →