Tuples, Sets, Dicts
Data Structures in Practice
Chapter 3 ยท Tuples, Sets, Dicts
Chapter 2 looked inside the list. This chapter covers three neighbours that solve problems a list handles badly: the tuple (a list that cannot change), the set (membership and uniqueness) and the dict (lookup by key). The last two rest on one idea, hashing, so the rules for what may be hashed come first.
Tuples: Fixed Shape
A tuple is an ordered row of references like a list, but it cannot be changed after creation: no append, no item assignment. That gives you a promise to the reader (this group of values is a single fixed record) and a practical benefit: a tuple of hashable items can itself be hashed.
The Hashing Rule
Sets and dict keys are stored by hash: a whole number computed from the value, used to jump straight to the right spot instead of scanning (the idea behind the constant-cost lookups in Data Structures in Practice 1). For that to work, a value's hash must never change while it is stored, so Python only allows hashable values, which in practice means immutable ones. Equal values must also have equal hashes.
So: numbers, strings, None, tuples of hashables and frozenset (an immutable set) may be keys; lists, sets and dicts may not. The tuple-as-key trick is the everyday payoff: coordinates, pairs and composite identifiers make natural keys.
Equal Values Collide
Because equal values must hash alike, 1, 1.0 and True are the same key:
One entry survives, keeping the first key but the last value. For your own classes, defining __eq__ without __hash__ makes instances unhashable (Python refuses to guess); define both, based on the same fields, to use them in sets:
Sets: Membership and Algebra
A set stores each value once and answers x in s by hashing, not scanning. It also supports the operations from school maths, which replace many hand-written loops:
| Question | Operator | Method |
|---|---|---|
| In either? | a | b | union |
| In both? | a & b | intersection |
| In a but not b? | a - b | difference |
| In exactly one? | a ^ b | symmetric_difference |
| Is a inside b? | a <= b | issubset |
| Nothing shared? | (none) | isdisjoint |
sorted(s) whenever order matters. Also, {} is an empty dict; an empty set is set().
Removing Duplicates
set(items) removes duplicates but loses the original order. To keep first-seen order, use dict.fromkeys, since a dict remembers insertion order (next section):
Dicts: Patterns You Will Reuse
A dict maps hashable keys to any values. Since Python 3.7 it remembers insertion order (a language guarantee, unlike sets). Re-assigning an existing key keeps its position; deleting and re-adding moves it to the end. Two dicts are equal when they hold the same pairs, regardless of order, which is different from lists.
Counting and Grouping
The two commonest jobs are counting things and collecting things by key. Plain dicts do both with get and setdefault; the standard library adds Counter and defaultdict to say it more directly.
Safe Lookups and Merging
In both merges the right-hand dict wins on a shared key. d[k] raises KeyError for a missing key; d.get(k, default) and d.pop(k, default) do not.
Challenges
Write can_be_key(value) that returns True if the value could be used as a dict key (use hash inside try/except). Test it on 5, "hi", (1, 2), [1, 2], (1, [2]), {1}, frozenset({1}) and None, and say which results come from the tuple rule.
Write unique_in_order(items) two ways: with dict.fromkeys, and with a seen set plus a result list. Check both give [3, 1, 2, 4] for [3, 1, 3, 2, 1, 4], and explain why plain list(set(items)) is not good enough.
Write group_by_first_letter(words) returning a dict from first letter to the list of words starting with it (use setdefault). Then, given the sets {"ann", "bob", "cy"} and {"bob", "dee"}, print the sorted union, intersection, difference and symmetric difference.
Quick Reference
- Tuple: fixed record; hashable if everything inside is hashable
- Hashable means immutable in practice; equal values must hash equal
- Set: unique values, fast membership, algebra with
| & - ^; never rely on order - Dict: key to value, insertion order guaranteed, equality ignores order
- Counting and grouping:
Counter,defaultdict,setdefault - Order-preserving unique:
list(dict.fromkeys(items))
deque, the queue module, and uses such as undo history and task queues.