LambdaLabTM
Computer Science · Class 11 · Dictionary Revisited
DictionariesPrograms⏱️ 15 min read

Building & Reshaping

Most dictionaries in real programs are not typed out — they are built from something else, or built from a dictionary that has the right facts in the wrong shape. Five patterns cover almost all of it, and every one of them is the same three lines: an empty dictionary, a loop, and one assignment.

1Program 1 — from two tuples that line up

📋 The problem

Given a tuple of names and a tuple of marks in the same order, build a name-to-mark dictionary.

The two tuples are joined by position names[2] belongs with marks[2] — so this is a job for the index loop:

from_two.py
# names[i] goes with marks[i]

names = ('Riya', 'Amit', 'Sara')
marks = (78, 85, 62)
report = {}

for i in range(len(names)):
    report[names[i]] = marks[i]

print(report)
Output
{'Riya': 78, 'Amit': 85, 'Sara': 62}
report = {}

The collector. Empty braces are an empty dictionary — () would be an empty tuple and [] an empty list.

for i in range(len(names)):

One index, used on both tuples. The item form cannot do this: it hands over a name with no way to find the mark that goes with it.

report[names[i]] = marks[i]

Assigning to a key that does not exist yet CREATES it. That is the whole build — there is no 'add' method to call.

Watch Out
The two tuples must be the same length. If marks is shorter, marks[i] raises IndexError: tuple index out of range partway through, and the dictionary is left half-built. Real code checks len(names) == len(marks) first.

When the data already arrives as rows, it is simpler still — no index at all:

from_rows.py
# from a tuple of records, which is the shape data usually has

rows = (('Riya', 78), ('Amit', 85), ('Sara', 62))
report = {}

for name, m in rows:
    report[name] = m

print(report)
Output
{'Riya': 78, 'Amit': 85, 'Sara': 62}

2Program 2 — swapping keys and values

📋 The problem

Turn a country-to-capital dictionary into a capital-to-country one.

invert.py
# the value becomes the key, and the key becomes the value

capital = {'India': 'Delhi', 'France': 'Paris', 'Japan': 'Tokyo'}
country = {}

for k, v in capital.items():
    country[v] = k

print(country)
Output
{'Delhi': 'India', 'Paris': 'France', 'Tokyo': 'Japan'}

country[v] = k — read it slowly. The value from the old dictionary is used as the key of the new one, which is why the line looks back to front.

Watch Out
Inverting only works when the values are all different. Keys are unique; values need not be. Invert a name-to-grade dictionary where two students share a grade and one of them silently disappears:
invert_clash.py
# Riya and Sara both got an A — watch what happens

grade = {'Riya': 'A', 'Amit': 'B', 'Sara': 'A'}
by_grade = {}

for name, g in grade.items():
    by_grade[g] = name

print(by_grade)
Output
{'A': 'Sara', 'B': 'Amit'}

Riya is gone. by_grade['A'] = 'Riya' ran, and then by_grade['A'] = 'Sara' overwrote it — because assigning to a key that already exists replaces its value. Nothing warned you.

The fix is to let each key hold a tuple of everyone who belongs to it, growing it as more arrive:

invert_keep_all.py
# each grade keeps everybody who got it

grade = {'Riya': 'A', 'Amit': 'B', 'Sara': 'A'}
by_grade = {}

for name, g in grade.items():
    if g in by_grade:
        by_grade[g] = by_grade[g] + (name,)
    else:
        by_grade[g] = (name,)

print(by_grade)
Output
{'A': ('Riya', 'Sara'), 'B': ('Amit',)}

The same shape as the frequency tally — first sighting makes the entry, later sightings add to it. The comma in (name,) is the same one-item-tuple comma from the tuples chapter, and leaving it out breaks this program in the same way.

3Program 3 — which keys have a given value?

Looking up by key is what a dictionary is for, and it takes one step. Looking up by value has no shortcut at all — it is a linear search, exactly like searching a tuple:

keys_with_value.py
# who got an A?

grade = {'Riya': 'A', 'Amit': 'B', 'Sara': 'A', 'John': 'C'}
wanted = 'A'

for name, g in grade.items():
    if g == wanted:
        print(name)
Output
Riya
Sara
Key Takeaway
A dictionary is fast in one direction only. grade['Riya'] goes straight there. “Everyone with an A” has to look at every pair. If a program keeps asking the value question, that is the sign it should have been built the other way round in the first place.

4Program 4 — a new dictionary of the ones that qualify

filter_new.py
# a smaller dictionary, built from a bigger one

salary = {'Riya': 42000, 'Amit': 55000, 'Sara': 38000, 'John': 61000}
seniors = {}

for name, pay in salary.items():
    if pay > 50000:
        seniors[name] = pay

print(seniors)
print(salary)
Output
{'Amit': 55000, 'John': 61000}
{'Riya': 42000, 'Amit': 55000, 'Sara': 38000, 'John': 61000}

The original is untouched — the loop only ever reads it. That is what makes filtering into a new dictionary the safe move, and it sidesteps the “changed size during iteration” error you would hit trying to delete the low earners as you walked.

5Program 5 — merging two dictionaries

merge.py
# update() pours the second dictionary into the first

term1 = {'Riya': 78, 'Amit': 85}
term2 = {'Sara': 62, 'Amit': 90}

term1.update(term2)

print(term1)
Output
{'Riya': 78, 'Amit': 90, 'Sara': 62}

Sara is new and gets added. Amit is in both, and the second dictionary wins — 85 became 90. That is the same overwrite rule as before, and here it is usually what you want.

Watch Out
update() changes term1 itself. The original term-1 marks are gone. When both are still needed, copy first:
merge_copy.py
# keep both: merge into a copy

term1 = {'Riya': 78, 'Amit': 85}
term2 = {'Sara': 62, 'Amit': 90}

both = term1.copy()
both.update(term2)

print('term1:', term1)
print('both: ', both)
Output
term1: {'Riya': 78, 'Amit': 85}
both:  {'Riya': 78, 'Amit': 90, 'Sara': 62}

6Program 6 — grouping a table by one column

The most useful of the lot, and the one that shows up in every data question ever asked: a table of rows, and a total per category.

📋 The problem

Given a tuple of (region, sales) rows, total the sales for each region.

sum_by_group.py
# one running total per region, made as each region first appears

sales = (('north', 1200), ('south', 900), ('north', 800),
         ('east', 400), ('south', 600))
totals = {}

for region, amount in sales:
    totals[region] = totals.get(region, 0) + amount

for region in sorted(totals):
    print(region, '->', totals[region])
Output
east -> 400
north -> 2000
south -> 1500
Key Takeaway
This is the frequency tally with + amount instead of + 1. That is the whole difference between counting and totalling, and it is the same difference as count = count + 1 versus total = total + m on a plain loop. Recognising it means you already know how to write this one.
count_by_group.py
# the counting version: how many students in each section?

students = (('Riya', 'A'), ('Amit', 'B'), ('Sara', 'A'),
            ('John', 'C'), ('Zoya', 'A'))
tally = {}

for name, sec in students:
    tally[sec] = tally.get(sec, 0) + 1

for sec in sorted(tally):
    print('Section', sec, '-', tally[sec], 'student(s)')
Output
Section A - 3 student(s)
Section B - 1 student(s)
Section C - 1 student(s)
sum_by_group.py

7Recap

Assigning to a new key creates it

d[k] = v is both 'add' and 'change'. There is no separate add method, and that is why an overwrite is so easy to do by accident.

Inverting needs unique values

Two keys sharing a value means one of them vanishes on the swap. Keep a tuple per key when that can happen.

Searching by value is a full walk

d[k] is one step; 'which keys hold this value' has to look at every pair. Build it the other way round if you keep asking.

Filter into a new dictionary

Deleting as you walk raises RuntimeError. Building a second one leaves the original intact and cannot go wrong.

d[k] = d.get(k, 0) + amount groups a table

The same line as the frequency tally, with the amount instead of 1. Counting and totalling per category are one pattern.

✍️ Now write these yourself
  1. 1

    Build a dictionary of squares — {1: 1, 2: 4, 3: 9, …} — for 1 to 10.

    Hint · for n in range(1, 11): and squares[n] = n * n. Nothing to loop over but the numbers.

  2. 2

    From a tuple of (item, price, qty) rows, build an item-to-line-total dictionary.

    Hint · Three names in the loop header, and totals[item] = price * qty.

  3. 3

    Build a dictionary of the items priced under 25.

    Hint · Program 4, with the test turned round.

  4. 4

    Invert a phone book, and say what happens if two people share a number.

    Hint · One of them disappears. Try the tuple-per-key version from program 2 to keep both.

  5. 5

    From a tuple of (city, temperature) readings taken on many days, print the highest temperature per city.

    Hint · A champion per key: if the city is new, store the reading; otherwise store it only when it beats what is there.

Quick Check

grade = {'Riya': 'A', 'Amit': 'B', 'Sara': 'A'}. What does the plain inversion produce?

Quick Check

Why build a filtered dictionary rather than delete from the original while looping?

Quick Check

What is the difference between the counting tally and the grouping total?