Lesson 2.4Lesson 2.4 · Control Flow & Collections
Dictionaries & Sets
Look things up by name, not by number - and answer what is unique - with the two collections that model real design data
You do not remember the kitchen as 'room number 1'. You remember it as 'the kitchen'. A dictionary lets your code do the same.
A list is perfect when order matters and you fetch things by position. But most design data is not really positional - you want the area of the kitchen, not the item at index 1. You think in names mapped to values: room to area, material to rate, drawing to revision. That mapping is exactly what a dictionary is.
The dictionary - dict for short - is one of the most important structures in Python, and it models design information astonishingly well. Alongside it we will meet the set, a collection that automatically keeps only unique items, which answers a question you ask constantly: what distinct things are on this project? Together with lists, these complete the core collections you need before moving on to functions and files.
dict = look up by name. for k, v in d.items(). set = only the unique ones. See 'per' or 'by'? use a dict.
The dictionary: keys mapped to values
A dictionary stores key -> value pairs inside curly braces. The key is what you look something up by; the value is what you get back. For design data, the key is usually a name and the value is a number, a string, or even another collection:
areas = {
"living": 24.0,
"kitchen": 12.5,
"bath": 4.5,
"bed1": 15.0,
}
print(areas["kitchen"]) # 12.5You fetch a value by putting its key in square brackets - areas["kitchen"] - and it comes back immediately, no matter how many entries the dictionary holds. That is the dictionary's superpower: lookup by key is fast and direct, where finding something in a list would mean scanning through it. The keys are unique - a dictionary cannot hold two "kitchen" entries, and assigning to an existing key overwrites its value. Asking for a key that does not exist, like areas["garage"], raises a KeyError; when you are unsure a key is present, use the .get() method, which returns None (or a default you supply) instead of crashing:
print(areas.get("garage")) # None - no error
print(areas.get("garage", 0.0)) # 0.0 - your defaultAdding, changing and iterating a dictionary
Dictionaries are mutable, so you add or update an entry just by assigning to a key - if the key exists it is updated, if not it is created:
areas["study"] = 9.0 # add a new pair
areas["bath"] = 5.0 # update an existing one
del areas["bed1"] # remove a pair
print(len(areas)) # how many pairs nowThe move you will use most is looping over a dictionary. Iterating it directly gives you the keys, but usually you want both key and value together, which is what .items() provides:
total = 0
for room, area in areas.items():
print(f"{room}: {area} sqm")
total = total + area
print(f"Total: {total} sqm")That for key, value in d.items(): pattern is the bread and butter of dictionary work - it walks every pair so you can report, total or transform them. There are two companions: .keys() gives just the names (for room in areas.keys():) and .values() gives just the numbers (sum(areas.values()) totals every area in one line). And the in keyword tests membership against the keys - if "kitchen" in areas: is a clean, crash-free way to check before you look up. Values themselves can be richer than single numbers: a room could map to a list of its finishes, or to another dictionary of its properties, which is how real project data - nested and structured - gets modelled in code. This is also, not coincidentally, the shape of the JSON files you will read in Module 3.
areas[key] = value adds or updates. for k, v in d.items(): is the everyday loop. 'in' tests the keys.
When to use a dict instead of a list
The two parallel lists from the last lesson - rooms and areas kept in step - were a warning sign. Whenever you find yourself maintaining two lists that must stay aligned by position, a dictionary is almost always the better structure, because it binds the name and the value into one unit that cannot drift out of sync:
# fragile - two lists that must stay aligned
rooms = ["living", "kitchen", "bath"]
areas = [24.0, 12.5, 4.5]
# robust - one dict, name bound to value
areas_by_room = {"living": 24.0, "kitchen": 12.5, "bath": 4.5}Choose a list when order is the point and you access by position or just process every item in sequence - a run of points, a sequence of drawings, a queue of files. Choose a dictionary when you look things up by a meaningful name, when each item has a natural identifier, or when you are counting or grouping. Counting is a classic dict job: to tally how many rooms use each finish, you loop once and use the finish name as a key, adding to its running count. Grouping is another - map each department name to a list of its rooms. As a rule of thumb: if the sentence describing your data contains the word 'per' or 'by' - area per room, cost by trade, count by material - you almost certainly want a dictionary.
It is worth saying plainly that these choices are not academic. Picking the wrong collection is one of the most common reasons a beginner's script becomes tangled and slow: scanning a list in a loop to find something when a dict would look it up directly; keeping three parallel lists in step by hand when one dict of records would hold them together; letting duplicates pile up in a list when a set would answer the real question in a line. The collection you choose shapes how clear and how fast everything downstream is, so it is worth a moment's thought at the start rather than a painful rewrite later.
Sets: what is unique
The last core collection is the set - an unordered collection that automatically holds only unique items. Make one from a list and every duplicate silently vanishes:
materials = ["oak", "teak", "oak", "glass", "teak", "oak"]
distinct = set(materials)
print(distinct) # {'oak', 'teak', 'glass'} - order not guaranteed
print(len(distinct)) # 3 - how many different finishesThis answers a question you ask on every project: what distinct things are here? How many different finishes across the scheme, which unique room types exist, what set of drawing numbers appears in a folder. A set does it in one line. Sets are also built for membership tests - checking whether an item is present with in is very fast, faster than scanning a list, which matters when you are checking thousands of items against a known collection. And they support the operations you learned in school as Venn diagrams: a & b gives what is in both (the intersection - say, materials used on both floors), a | b gives everything in either (the union), and a - b gives what is in the first but not the second (the difference - rooms specified but not yet drawn). The trade-off is that a set has no order and no indexing - you cannot ask for distinct[0] - and it cannot contain duplicates by definition. Reach for a set specifically when uniqueness or fast membership is the point; reach for a list when order and repeats matter; reach for a dict when you look up values by a key. With those three in hand, you can model almost any design data a script will meet.
Putting it together: counting and grouping
Two dictionary patterns come up so constantly in design work that they deserve to be learned as set pieces: counting and grouping. Both use a key to accumulate, and both are how you turn a flat list into a summary.
Counting answers 'how many of each?'. Walk a list once and use each item as a key whose value is a running tally. The .get(key, 0) method makes it clean by supplying a starting count of zero for any key not yet seen:
materials = ["oak", "teak", "oak", "glass", "teak", "oak"]
counts = {}
for m in materials:
counts[m] = counts.get(m, 0) + 1
print(counts) # {'oak': 3, 'teak': 2, 'glass': 1}That is a materials take-off in five lines - how many times each finish appears across a scheme - and the identical shape counts rooms per type, drawings per discipline, or fittings per supplier.
Grouping answers 'which items belong to each category?'. Here each value is a list, and you append to it. Use .setdefault(key, []) to start an empty list the first time a key appears, then add to it:
rooms = [("living", "public"), ("kitchen", "service"), ("bath", "service")]
by_zone = {}
for name, zone in rooms:
by_zone.setdefault(zone, []).append(name)
print(by_zone) # {'public': ['living'], 'service': ['kitchen', 'bath']}That collects rooms by zone in a single pass - the raw move behind a departmental area schedule or a finishes matrix grouped by room type. Notice that the value is now a list living inside a dict, which is the everyday face of nesting: dicts holding lists, lists holding dicts, dicts holding dicts. A single room might be a dict of its own - {"name": "kitchen", "area": 12.5, "finishes": ["tile", "laminate"]} - and a whole project a list of such dicts. That nested shape is not an advanced curiosity; it is precisely how JSON stores data and how you will receive information from files, spreadsheets and design software in the modules ahead. Master counting, grouping and a comfort with nesting, and you have the core of practical data handling - everything Module 3 and the pandas work in Module 4 build on is a richer version of these same few moves over these same few collections.
Step back and the whole module fits in a sentence: conditionals let a script decide, loops let it repeat, and lists, dictionaries and sets let it hold data in the shape that matches the question being asked. Almost everything you will build from here - reading files, cleaning a spreadsheet, generating a grid of geometry, extracting data from a model - is these few pieces recombined over larger and messier data. There is no secret extra layer waiting in the advanced modules; there is only more practice fitting these same moves to real problems. If the four lessons of this module feel solid, you are genuinely ready for the rest of the course, because the rest of the course is this module applied.
dict { }
A mutable map of unique keys to values
Look up by key, fast and direct. Keys are unique; re-assigning a key overwrites. Models room->area, material->rate naturally.
.items() / .keys() / .values()
Iterate a dict's pairs, keys, or values
for k, v in d.items(): is the everyday loop. sum(d.values()) totals; .keys() lists the names.
.get() and 'in'
Crash-free lookup and membership test
d.get(key, default) avoids a KeyError; 'key in d' checks a key exists before you use it.
set()
An unordered collection of unique items
Duplicates vanish automatically. Fast membership tests; supports &, |, - for intersection, union, difference. No indexing.
KeyError
The error from asking for a key that is not there
The dict equivalent of a list's IndexError. Guard with .get() or an 'in' check when a key may be missing.
Workshop - an area schedule and a materials audit
Model a small flat as a dictionary of room -> area, then run the reports a schedule needs, and use a set to consolidate a messy materials list. This is the shape of real project data in code.
Python 3 in any editor or notebook. No libraries needed.
Goal: build a room->area dict, report on it, and find unique materials with a set
Inputs: `areas = {"living": 24.0, "kitchen": 12.5, "bath": 4.5, "bed1": 15.0}` and a `materials` list with repeats
Time: ~30 minutes- 1Look up and print the area of the kitchen by key. Then use
.get("garage", 0.0)to show it returns a default rather than crashing on a missing room. - 2Add a
"study": 9.0pair and update"bath"to5.0by assignment, then print the dictionary to confirm both took effect. - 3Loop over
areas.items()to print each room and its area on its own line, accumulating atotal; then print the same total in one line withsum(areas.values())and confirm they match. - 4Use an
if "kitchen" in areas:check to safely report the kitchen area, and anelseto report if it is missing. - 5Given
materials = ["oak", "teak", "oak", "glass", "teak", "oak"], buildset(materials)and print how many DISTINCT finishes there are withlen(). - 6Bonus - counting by key: loop over
materialsand build a NEW dictcountswhere each finish maps to how many times it appears (usecounts.get(m, 0) + 1). Print it to see oak: 3, teak: 2, glass: 1.
You’ll walk away with
A script that looks up and safely defaults dict values, adds/updates/reports pairs with an .items() loop and a one-line total, guards a lookup with 'in', reports the number of distinct materials with a set, and counts occurrences per material into a new dict.
Three altitudes on the same idea
Read the band that fits you — or all three.
A dictionary is how structured project data lives in a script. Room-to-area, sheet-to-revision, element-to-parameter - each is a mapping you look up by name and iterate with .items(). Totalling area by department, checking whether a required room is present, or counting elements by type are all dict jobs. Sets answer the audit questions - the distinct room types in a model, the unique sheet numbers in an issue, the parameters present on both of two elements. When you later pull data out of Revit, it arrives as exactly these dicts and lists of dicts.
Your spec data is naturally a dictionary. A finish schedule is room -> finish; a rate card is material -> cost; a selection is item -> product. Holding it as a dict lets you look up, update and total by name rather than juggling parallel lists, and .items() walks the whole schedule for a report or a re-price. A set instantly answers 'how many distinct finishes am I specifying?' or 'which materials appear in both the master bed and the study?' - the kind of consolidation question that otherwise means eyeballing a spreadsheet.
The dictionary is the structure that unlocks real data work, so learn it as thoroughly as the list. Lookup by key, iteration with .items(), counting and grouping by key, and the crash-free .get() and in checks are patterns you will use in every data-handling task, and they are the exact shape of JSON and of pandas' columns. Sets teach you to think about uniqueness and membership cleanly. Together they are what let the pandas, geometry and BIM modules treat messy real-world data as something structured you can command.
“A dictionary keeps its items in the order I add them, and I can get an item by its position like `d[0]`.”
d[0] in a dictionary does not fetch the first pair - it looks up the key 0, and if no such key exists it raises a KeyError. Dictionaries are addressed by key, never by position, so there is no 'first item by number'. Second, order: modern Python (3.7 and later) does remember insertion order when you iterate, so pairs come back in the order you added them - but you should not rely on order as the way you find things. The whole point of a dict is lookup by meaningful key; if position genuinely matters, you want a list, or a list of tuples. Treat a dictionary as an unordered map from names to values that happens to preserve insertion order for display, and reach into it only by key.Do it yourself
Predict each result before you run it.
- 1How do you fetch the value for the key
"kitchen"from a dict calledareas? - 2What does
areas.get("garage", 0.0)return if there is no garage, and why is that safer thanareas["garage"]? - 3Write the loop that prints every room and its area from a dict using
.items(). - 4You have two parallel lists that must stay aligned. Why is a dictionary usually better?
- 5What does
set(["oak", "teak", "oak"])produce, and how many items does it contain?
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Associative array — Wikipedia, 2026.
- 02Set (abstract data type) — Wikipedia, 2026.
- 03The Python Tutorial — Python Software Foundation, 2026.
- 04Data type — Wikipedia, 2026.
.get() and in guard against missing keys, and for k, v in d.items(): walks every pair. Prefer a dict over parallel lists whenever you access data by name or count and group 'by' something. A set holds only unique items, answers 'what is distinct?' in one line, and supports fast membership tests and intersection/union/difference. Lists, dicts and sets together are your core collections.You can now decide, repeat, and hold data in the right shape - the whole toolkit of control flow and collections. Module 3 puts it to work beyond a single script: bundling logic into reusable functions, importing libraries, and reading and writing real files so your code touches the world outside the editor.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →