EE1021 Programming for Engineers · Lecture 5 · 2026–2027
sensor_ids = ["S1", "S2", "S3"]
readings = [4.92, 5.31, 4.97]
statuses = ["normal", "high", "normal"]To find the status of S2, which assumptions must remain true about all three lists?
The lists must have equal lengths and matching order. A dictionary can connect each sensor ID directly to its data.
Choose list, tuple, dictionary, or set for each row.
| # | What we need to store | Choice and reason |
|---|---|---|
| 1 | Voltages from one sensor, added as measured e.g. 4.92, 5.08, 5.11, then 4.97 arrives |
list samples.append(4.97): the list grows, and the measurement order is kept |
| 2 | A sensor’s ID and its unit, which never change e.g. S1 and V |
tuple ("S1", "V"): a small group of values that stays the same |
| 3 | The latest reading of each sensor, looked up by ID e.g. S1 → 4.92, S2 → 5.08 |
dictionary {"S1": 4.92, "S2": 5.08}: give the ID, get the reading |
| 4 | The sensors that raised an alarm, each listed once e.g. S2, S3, S2 → S2, S3 |
set {"S2", "S3"}: a repeated ID is kept only once |
List lookup by position
| index | value |
|---|---|
| 0 | 4.92 |
| 1 | 5.31 |
| 2 | 4.97 |
Dictionary lookup by meaning
| key | value |
|---|---|
| S1 | 4.92 |
| S2 | 5.31 |
| S3 | 4.97 |
A dictionary is a mutable mapping: key → value.
Direct indexing is appropriate when the key must exist. A missing required key often indicates a program or data error.
The optional second argument supplies a default without changing the dictionary.
When would a default value hide a genuine data error rather than handle an optional key?
readings = {"S1": 4.92, "S2": 5.31}
readings["S3"] = 4.97 # new key: add
readings["S2"] = 5.08 # existing key: replace valueAfter both assignments:
readings = {"S1": 4.92, "S2": 5.08, "S3": 4.97}
del readings["S3"]
removed = readings.pop("S2")
fallback = readings.pop("S9", None)readings = {"S1": 4.92, "S2": 5.08}
"S1" in readings # True: tests keys
4.92 in readings # False: not a key
4.92 in readings.values() # True: explicitly tests valuesDictionary membership tests keys unless a values view is requested.
Common keys (immutable values):
Not valid as keys (mutable values):
locations = {(2, 3): "sensor-A"} # valid tuple key
# {[2, 3]: "sensor-A"} # TypeError: a list cannot be a keyAvoid measured floating-point quantities as keys when exact equality is uncertain.
Predict the entries and their values.
Check: {“S1”: 4.92, “S2”: 5.08, “S3”: 4.97}. A repeated key replaces its value.
readings = {"S1": 4.92, "S2": 5.08, "S3": 4.97}
for sensor_id in readings:
print(sensor_id, readings[sensor_id])Direct iteration produces keys. Writing readings.keys() is valid but unnecessary here.
The pair from items() is unpacked into two loop variables.
readings = {"S1": 4.92, "S2": 5.08, "S3": 4.97}
total = 0
for value in readings.values():
total += value
average = total / len(readings)
print(round(average, 2))The dictionary supplies meaningful lookup; its values can still be accumulated like a list.
events = ["normal", "high", "normal", "low", "normal"]
counts = {}
for event in events:
if event in counts:
counts[event] += 1
else:
counts[event] = 1Each event is either a first occurrence or an update to an existing count.
Output: {“normal”: 3, “high”: 1, “low”: 1}
The default 0 represents a count before the first occurrence.
counts = {}
for code in ["A", "B", "A"]:
counts[code] = counts.get(code, 0) + 1
print(counts.get("A", 0), counts.get("C", 0))Check: 2 0. The missing C key is not added by get.
events = ["S1", "S2", "S1", "S3", "S2"]
unique_sensors = set(events)
"S2" in unique_sensors # True
len(unique_sensors) # 3A set answers “Have we seen this?” and “How many distinct values?” without tracking positions.
active = {"S1", "S2", "S3"}
calibrated = {"S2", "S3", "S4"}
active | calibrated # union: S1, S2, S3, S4
active & calibrated # intersection: S2, S3
active - calibrated # difference: S1
active ^ calibrated # exactly one set: S1, S4Read the expression as a relationship between groups, not as a numeric calculation.
faulty = {"S3"}
faulty.add("S5")
faulty.remove("S3") # KeyError if absent
faulty.discard("S9") # no error if absentUse remove when absence is an error; use discard when absence is acceptable.
Write expressions for:
Check: active & calibrated; active - calibrated; active ^ calibrated.
readings = {"S1": 4.92, "S2": 5.31, "S3": 4.97}
calibrated = {"S1", "S3"}
status_counts = {}
for sensor_id, value in readings.items():
status = "normal" if 4.75 <= value <= 5.25 else "alarm"
status_counts[status] = status_counts.get(status, 0) + 1
needs_calibration = set(readings) - calibratedThe counting algorithm does not change when the event list comes from a file.
with open("sensor_events.txt", encoding="utf-8") as file:
events = file.read().split()
counts = {}
for event in events:
counts[event] = counts.get(event, 0) + 1The lab includes sensor_events.txt. Paths, modes, exceptions, line iteration, and CSV are taught in Lecture 10.
Without running Python:
Check: readings.get(“S9”, 0.0); dictionary keys; active - calibrated.
Summary: dictionaries map meaningful keys to values; sets represent unique membership. Both solve problems that positional lists handle poorly.