Today's outcomes
- Construct a mapping from key-value pairs.
- Predict replacement when keys repeat.
- Compare comprehensions with explicit accumulation.
Before you start: Complete Day 14 and its practical challenge.
Understand the concept
A dictionary comprehension builds a mapping
A dictionary comprehension has a key expression and value expression separated by a colon, followed by iteration and optional filtering. {topic: minutes for topic, minutes in pairs} maps each topic to a minute count. As with a list comprehension, the resulting outer dictionary is new.
The mapping's keys must be hashable and unique in the result. If several input pairs produce the same key, later assignments replace the earlier value. The comprehension does not keep every input record simply because it inspected every record. Its output count can be smaller than the number of admitted inputs.
The same issue appears when deriving normalized keys. "Lists" and " lists " may become one key under a trim-and-casefold rule. That can be useful for a summary, but replacement is not the same as adding the observations together. Choose whether repeated keys should be rejected, summed, grouped, or deliberately replaced.
Transforming values is different from aggregating records
A one-to-one value transformation is a good comprehension use: convert every known minute total to hours, or keep only selected fields from a record. Aggregation carries state across several observations and may be clearer as an ordinary loop.
For example, a report by topic might need to sum 30 minutes and 45 minutes under the same normalized topic key. A plain mapping comprehension retains only the later value 45. Both programs can return a plausible dictionary, but only the accumulation loop implements the intended total of 75.
Filtering must preserve valid boundary values
Filtering with if minutes excludes zero, not just None. Use an explicit missing-value check when zero is valid. Also keep label policy separate from numeric validity: a blank topic label and a negative duration are two different rejection reasons.
Today's accepted numerical inputs are nonnegative integers or None. The function will count blank labels and missing observations as ignored under its specific contract. Day 17 adds stricter validation for arbitrary external values. Do not broaden today's function promises beyond the documented input domain.
Worked example
pairs = [("lists", 30), ("functions", 60), ("lists", 45)]
last_value = {topic: minutes for topic, minutes in pairs}
totals = {}
for topic, minutes in pairs:
totals[topic] = totals.get(topic, 0) + minutes
hours = {topic: minutes / 60 for topic, minutes in totals.items()}
assert last_value == {"lists": 45, "functions": 60}
assert totals == {"lists": 75, "functions": 60}
assert hours == {"lists": 1.25, "functions": 1.0}
print("Last-value mapping:", last_value)
print("Accumulated totals:", totals)
| Policy | Result for lists | Interpretation |
|---|---|---|
| Later value replaces earlier value | 45 | Latest assignment under this input order |
| Sum every observation | 75 | Total minutes for the topic |
| Preserve a list of observations | 30 and 45 | History available for later analysis |
There is no universally correct collision policy. A current-status lookup might intentionally use a verified latest record, while a measurement history should retain every observation. “Keep last” only represents the latest chronological record if source order actually follows the intended time rule.
Guided practice
- Predict: build a comprehension from
[('A', 1), ('A', 2), ('B', 3)]. Compare its output count with its input count and identify the overwritten value. - Modify: normalize the topic keys before accumulating. With
('Lists', 30)and(' lists ', 45), expect one normalized key totaling 75. - Reconstruct: transform the accumulated totals into formatted hour strings using a dictionary comprehension. Keep the numeric totals separately so display formatting does not destroy data needed for further calculations.
For each mapping transformation, ask whether keys can collide. Checking only the final total may miss that one record was replaced by another with the same amount. Retain counts or an audit when reconciliation matters.
Daily challenge
Implement totals_by_topic(records). Each record has topic text and minutes containing a nonnegative integer or None. Trim and casefold topics. Ignore records with a blank topic or missing minutes, preserve a known zero, and sum repeated normalized topics. Return the totals mapping and ignored_count.
| Record | Contribution |
|---|---|
| Lists, 30 | lists gains 30 |
lists, 45 |
lists gains another 45 |
| Functions, 0 | functions exists with total zero |
| blank topic, 60 | ignored |
| Strings, None | ignored |
Expect {'lists': 75, 'functions': 0} and ignored_count 2. For an empty input, expect an empty dictionary and zero ignored records. The challenge requires accumulation, so do not force the entire calculation into a comprehension.
Reference solution
Compare after attempting the challenge
def totals_by_topic(records):
totals = {}
ignored = 0
for record in records:
key = record["topic"].strip().casefold()
minutes = record["minutes"]
if not key or minutes is None:
ignored += 1
continue
totals[key] = totals.get(key, 0) + minutes
return totals, ignored
fixture = [
{"topic": "Lists", "minutes": 30},
{"topic": " lists ", "minutes": 45},
{"topic": "Functions", "minutes": 0},
{"topic": " ", "minutes": 60},
{"topic": "Strings", "minutes": None},
]
assert totals_by_topic(fixture) == ({"lists": 75, "functions": 0}, 2)
assert totals_by_topic([]) == ({}, 0)
print(totals_by_topic(fixture))
The get default initializes a topic's additive total to zero. That is appropriate for an accumulation baseline; it is not converting a missing individual observation into zero. The missing-observation branch still excludes None explicitly.
Debug and explain
| Symptom | Investigate | Useful check |
|---|---|---|
| Fewer records than expected | Duplicate or normalized keys | Compare admitted count and distinct keys |
| Total equals the final observation | Replacement used instead of addition | Two records under one key |
| Zero-only topic disappears | Truth-value filter | One known-zero record |
| Mapping grows unpredictably | Inconsistent normalization | Mixed spelling fixture |
Explain why this solution uses a loop for accumulation and why a comprehension is still appropriate for the later hours conversion. Give a domain where keeping the final value is sensible, and name the evidence needed to interpret “final” as “current.”
Review and next step
Save the collision example and accumulation fixture. Rebuild the policy from a written contract during your next review. Day 16 saves text to local files, making preservation and round-trip checks part of your workflow.
Sources and verification
Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.
Check your understanding
Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.
Review your practical work
Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.
Pass the knowledge check and finish the practical self-review to save completion.