Iterable Objects II — Class Notes

Try me

Open In ColabBinder

These notes summarize the companion notebook Iterable Objects II and add practice cards and takeaways for your exam prep.

Key concepts: Tuples • Dictionaries • Nested Iterables • Comprehensions Extra concepts: Packing/Unpacking (*, **) • zip()

Introduction

Motivation

  • When we think of programming, we often think of arrays or lists of data.

  • However, real-world data is often more complex, and structured.

  • In real applications (like the IoT challenge), we need the right data structures to model and manipulate data effectively.

  • We need more design choices, data structures built for protection, fast access, or ease of use.

Objectives

  • Pass the basics and understand advanced iterable objects in Python.

  • Focus on two foundational data structures: tuples and dictionaries.

  • Specific objectives:

    • Pull the key differences between lists, tuples, and dictionaries.

    • Understand powerful techniques (nesting, and comprehensions).

  • This will give you different design options to manage data in your programs.

Intro for non-programmers

  • Lists are like arrays: ordered collections of items.

  • Tuples are like lists, but you can’t change them (immutable). Think of them as constants. A layer of protection (prevents unwanted/accidental changes down the line - data integrity). Brings performance wins (pre-allocation - Python knows final size).

  • Dictionaries: like address books: you look up values by keys (names), not positions. Useful when you need meaningful labels (not just numbers). Easier access to data (closer to how we find info in the real world). Plus, they are also faster than arrays!

  • Nesting: putting lists/dicts inside each other to model complex data (like a spreadsheet or a matrix).

  • Comprehensions: concise way to create lists/dicts by transforming/filtering existing ones.

Agenda

Class overview:

  • Intro & agenda (10 min)

  • Recap: Lists (15 min)

  • Tuples (10 min)

  • Dictionaries (20 min)

  • Card break (7 min)

  • Nested iterables (15 min)

  • Comprehensions (15 min)

  • Card break (7 min)

  • wrap-up (6 min)

  • Homework: Check extra contents and mini-challenges

0) Brief recap: Lists

  • What: Ordered, indexable, mutable sequences.

  • Mutable: can change size and content.

  • Syntax: lst = [1, 2, 3]

  • Indexing/Slicing: lst[i], lst[a:b:c]

  • Useful functions and methods: len(lst), lst.append(x)

[ ]:
row = ["Peter", "Paul", "Mary", "John", "Lucy"]
# Example 1: head
s = int(input("start index: "))
e = int(input("end index: "))
print("Selected names:", row[s:e])
[ ]:
# Example 2: rolling update
last_values = [2.3, 2.5, 2.7, 2.9]
i = 0
while True:
    new_value = float(input("New value (empty to stop): "))
    if not new_value:
        break
    last_values[i%len(last_values)] = new_value
    i += 1
    print("Rolling average:", sum(last_values)/len(last_values))

1) Tuples (immutable sequences)

What: Ordered, indexable sequences like lists, but immutable. Why: Use for fixed records.

Syntax & rules

  • Literal: t = (1, 2, 3) (the comma makes the tuple; (x,) is a 1‑tuple)

  • Indexing/Slicing: Same as lists →t[i], t[a:b:c] (read‑only)

  • Useful functions and methods: len(t), t.index(x)

[ ]:

# Example unknowns = ("x", "y", "z") print("First:", unknowns[0]) print("Tail:", unknowns[1:]) try: # This allows us to demonstrate immutability unknowns[0] = "X" # immutability demo except TypeError as e: print("TypeError:", e.__class__.__name__)

2) Dictionaries (mapping values by keys)

What: Key → value store (unordered). Similar to lists, but indexed by keys instead of positions. Why: Fast lookups by key; model records, easier to program. Create:

  • empty dictionary d = {},

  • init with a key value paird = {"k": 1}, Update:

  • Insert a new value d["l"] = 2 Remove: del d["k"], d.pop("k") Lookup: "k" in d (by key), v in d.values() (by value)

[ ]:

contacts = {} contacts["Paco"] = 655555555 contacts["Pepe"] = 666555111 contacts["Pili"] = 677777555 print("All:", contacts) # Literal init contacts = {"Paco": 655555555, "Pepe": 666555111, "Pili": 677777555} print("Keys:", list(contacts.keys())) print("Has 'Paco'?", "Paco" in contacts) print("Has number 655... as a VALUE?", 655555555 in contacts.values()) # Removal d = contacts.copy() # d = contacts is a shallow copy! changes will affect both d and contacts del d["Pepe"] # Remove pili = d.pop("Pili") # Retrieve & remove. Nice if you need the value. print("After removals:", d)

2.1) Iteration patterns

  • in tests membership on keys: "k" in d True if key exists.

  • Keys (default): for k in d: or for k in d.keys():

  • Values: for v in d.values():

  • Pairs: for k, v in d.items():

[ ]:
# Iteration
for name in contacts:
    print("Key only:", name, "→", contacts[name])

for name, phone in contacts.items():
    print("Pair:", name, phone)

2.2) Safe read:

  • d[key] raises KeyError if key missing.

  • Method d.get(key) returns None if key missing.

  • Use d.get(key, default) to avoid KeyError if key missing.

[ ]:
d = {"a": 1, "b": 2}
print("d['a']:", d["a"])
print("d.get('a'):", d.get("a"))
print("d.get('c'):", d.get("c"))          # missing key
print("d.get('c', 42):", d.get("c", 42))  # missing key with default

Exercise Cards (Peer Instruction)

How to use: predict → discuss → run the check cell (where provided). Keep answers short (what + why).

t = ("A", "B", "C")
t[0] = "Z"

Q: Does this run without exceptions? If not, what is the error?

Given:

contacts = {"Paco": 655555555, "Pepe": 666555111}
Q: Which is True? Choose all that apply.
"Paco" in contacts655555555 in contacts655555555 in contacts.values()
[ ]:

contacts = {"Paco": 655555555, "Pepe": 666555111} print("A:", "Paco" in contacts) print("B:", 655555555 in contacts) print("C:", 655555555 in contacts.values())
d = {"x":1, "y":2}
for k, v in d.items():
    print(f"square of {k} is {v**2}")

Q: What is printed?

[1]:

d = {"x":1, "y":2} for k, v in d.items(): print(f"square of {k} is {v**2}")
square of x is 1
square of y is 4

3) Nested iterables

  • List of lists (matrix‑like): [[1,2],[3,4]]

  • List of dicts (records): [{"name":"Pepe","age":19}, ...]

  • Dicts with list values: {"colors": ["Orange","Blue"]}

Indexing through levels: data[1]["name"], matrix[0][0]

[ ]:

# List of lists grid = [[1, 2], [3, 4]] print("grid[0] ->", grid[0]) print("grid[0][0] ->", grid[0][0]) # List of dicts students = [{"name": "Pepe", "age": 19}, {"name": "Ingrid", "age": 18}] print("Second student's name:", students[1]["name"])


[ ]:
## Mini challenge
# Print the age of student Ingrid

[ ]:
# Mini challenge
## For each of the two nested lists below, find the value 1 using indexing and print it
# Mini challenges
a = [[0, 0], [1,0]]
b = [[[0, 0], [0, 0]], [[1, 0], [0, 0]]]

3.1) Iteration over nested structures

  • Access specific fields during iteration:

for student in students:
    print(student["name"])
  • Iterate outer, then inner:

records = [{"name":"A","age":19},{"name":"B","age":18}]
for record in records:
    for key, value in record.items():
        ...
[ ]:
records = [{"name":"A","age":19},{"name":"B","age":18}]
for record in records:
    print(record["name"])

for record in records:
    for key, value in record.items():
        print(f"{key}: {value}")

[ ]:
# Mini challenge:
##  Use a for loop to find the average temperature

arduino_log = [{"time": "2022 - 08 - 31 00: 15", "temp": 25.6, "humidity": 66},
               {"time": "2022 - 08 - 31 00: 45", "temp": 25.9, "humidity": 67},
               {"time": "2022 - 08 - 32 00: 00", "temp": 25.7, "humidity": 66},
               {"time": "2022 - 08 - 32 00: 15", "temp": 25.6, "humidity": 65}]

4) Comprehensions

List comp: [expr for x in xs if cond] Dict comp: {key_expr: val_expr for ...}

Good for: small, readable transformations/filters. Prefer loops for complex logic.

[ ]:

keys = ('a', 'b', 'c', 'd') values = (1, 2, 3, 4) squares = [i*i for i in values] even_squares = [i*i for i in values if i % 2 == 0] new_dict = {keys[i]: squares[i] for i in range(len(keys))} print("squares:", squares) print("even_squares:", even_squares) print("new_dict:", new_dict)
[ ]:
# Tiny "average grade" demo without input()
samples = [{"name":"A", "grade": 7.5},
           {"name":"B", "grade": 9.0},
           {"name":"C", "grade": 6.5}]
avg = sum(s["grade"] for s in samples)/len(samples)
print("Average grade:", round(avg, 2))
[ ]:
# Mini challenge
# Use comprehension to find the average humidity
arduino_log = [{"time": "2022 - 08 - 31 00: 15", "temp": 25.6, "humidity": 66},
               {"time": "2022 - 08 - 31 00: 45", "temp": 25.9, "humidity": 67},
               {"time": "2022 - 08 - 32 00: 00", "temp": 25.7, "humidity": 66},
               {"time": "2022 - 08 - 32 00: 15", "temp": 25.6, "humidity": 65}]

4.2) Common patterns

  • Extract field list: [r["name"] for r in records]

  • Filter records: [r for r in records if r["age"] >= 18]

  • Build dict from list: {r["name"]: r["age"] for r in records}

  • Aggregate: sum(r["age"] for r in records) / len(records)

[ ]:
records = [{"name":"Samantha Eve","age":19},{"name":"Mark Grayson","age":18},{"name":"Amber Bennet","age":17}]
# Minichallenge: Discuss in peers and understand the following comprehensions
names = [r["name"] for r in records]
adults = [r for r in records if r["age"] >= 18]
age_dict = {r["name"]: r["age"] for r in records}
avg_age = sum(r["age"] for r in records) / len(records)
print("Names:", names)
print("Adults:", adults)
print("Age dict:", age_dict)
print("Average age:", avg_age)

Exercise Cards (Peer Instruction)

How to use: predict → discuss → run the check cell (where provided). Keep answers short (what + why).

students = [{"name":"A","age":19},{"name":"B","age":18}]

Q: Predict: students[1]["age"] and students[0]["name"].

[ ]:

students = [{"name":"A","age":19},{"name":"B","age":18}] print(students[1]["age"], students[0]["name"])
vals = [0,1,2,3,4]
out  = [v*v for v in vals if v%2==0]

Q: What is out?

[ ]:

vals = [0,1,2,3,4] out = [v*v for v in vals if v%2==0] print(out)

Map letters to their squared index (0-based) for "abcd":

letters = "abcd"
# result should be: {'a':0, 'b':1, 'c':4, 'd':9}

Q: Predict or sketch the comp; then run the check.

[ ]:

letters = "abcd" res = {ch: i*i for i, ch in enumerate(letters)} print(res)

Extra (Not covered in exam)

5) Packing & Unpacking (*, **)

  • Tuple unpack: a, b = (1, 2); extended: first, *mid, last = t

  • Call-time unpack: f(*args_tuple); keyword unpack: g(**kwargs_dict)

  • Useful with functions like range, print.

[ ]:

t = (1, 2, 3, 4) a, b, c, d = t print(a, b, c, d) first, *g, last = t print("first:", first, "| g:", g, "| last:", last) # Call-time unpack range_args = (1, 10, 3) print("range(*range_args):", list(range(*range_args))) args = ("first string", "second string") key_args = {"sep": ", ", "end": "\n"} print(*args, **key_args)

6) zip()

What: Combine iterables position-wise into tuples. Stops at the shortest input. Unzip: a, b = zip(*pairs) (works if not empty).

[5]:

x = (1, 2, 3) y = (4, 5, 6, 7) k = ("a", "b", "c") pairs = list(zip(x, y)) pairs_dict = dict(zip(k, x)) # k: x mapping nice pattern for dicts print("pairs:", pairs) print("pairs_dict:", pairs_dict) # Unzip (guard empty) if pairs: a, b = zip(*pairs) print("unzipped a:", a, "| b:", b)
pairs: [(1, 4), (2, 5), (3, 6)]
pairs_dict: {'a': 1, 'b': 2, 'c': 3}
unzipped a: (1, 2, 3) | b: (4, 5, 6)

Extra Cards

Card 7 — Nested iterables

What is the output of:

data = {"colors": ["Red", "Green", "Blue"], "numbers": [1, 2, 3]}
print(data["colors"][1])
[ ]:
data = {"colors": ["Red", "Green", "Blue"], "numbers": [1, 2, 3]}
print(data["colors"][1])

Card 8 - Comprehension vs loop

Which of these snippets produces the same result?

# Snippet A
result_A = []
for i in range(5):
    if i % 2 == 0:
        result_A.append(i * i)
# Snippet B
result_B = [i * i for i in range(5)]
# Snippet C
result_C = [i * i for i in range(5) if i % 2 == 0]
# Snippet D
result_D = sum([i * i for i in range(5) if i % 2 == 0])
[7]:
# Snippet A
result_A = []
for i in range(5):
    if i % 2 == 0:
        result_A.append(i * i)
# Snippet B
result_B = [i * i for i in range(5)]
# Snippet C
result_C = [i * i for i in range(5) if i % 2 == 0]
# Snippet D
result_D = sum([i * i for i in range(5) if i % 2 == 0])

print(f"result_A: {result_A}")
print(f"result_B: {result_B}")
print(f"result_C: {result_C}")
print(f"result_D: {result_D}")

result_A: [0, 4, 16]
result_B: [0, 1, 4, 9, 16]
result_C: [0, 4, 16]
result_D: 20

Card 9 — Scalar product with comprehension

What is the output of:

a = [1, 2, 3]
b = [4, 5, 6]
c = sum(a[i] * b[i] for i in range(len(a)))
[ ]:
a = [1, 2, 3]
b = [4, 5, 6]
c = sum(a[i] * b[i] for i in range(len(a)))

Card 10 - Series with comprehension

Which of these snippets produces a geometric series with common ratio of \(\frac{1}{2}\), that is (\(\frac{1}{2} + \frac{1}{4} + \frac{1}{8} + ...\) up to n? ```python n = 5 # Snippet A s_A = sum(1 /2 for i in range(n)) # Snippet B s_B = sum(1 / (2 ** i) for i in range(1, n + 1)) # Snippet C s_C = sum(1 / (2 ** i) for i in range(n))

[8]:
n = 5
# Snippet A
s_A = sum(1 /2 for i in range(n))
# Snippet B
s_B = sum(1 / (2 ** i) for i in range(1, n + 1))
# Snippet C
s_C = sum(1 / (2 ** i) for i in range(n))

print(f"s_A: {s_A}")
print(f"s_B: {s_B}")
print(f"s_C: {s_C}")
s_A: 2.5
s_B: 0.96875
s_C: 1.9375

Mini Challenges (apply patterns)

  1. Filter + transform: From students (list of dicts with grade), build a list of names of students with grade >= 8, uppercased. (One line comprehension or a tiny loop.)

[ ]:


  1. Dict from pairs: Given keys = ["id","name","age"] and row = [101,"Ada",28], build {"id":101,"name":"Ada","age":28} using zip.

[ ]:

Takeaways

  • Tuples are immutable: safe to share, great for fixed records and unpacking.

  • Dictionaries store by key; test membership on keys (k in d) or on values (v in d.values()).

  • Nested data is just layers: index your way down (data[1]["x"][0]).

  • Comprehensions are ideal for small, clear transformations; prefer loops if it gets dense.

  • * and ** unpack positional/keyword arguments—handy with range, print, and functions you write.

  • ``zip`` combines; zip(*pairs) unzips; stops at the shortest input.

  • Aim for minimal changes to fix intent (great exam habit).