MGU Computer Science Series — Programming with Python
Part IV: Data Structures
Lists · Dictionaries · Records · Nesting · Tracing
Chapter 19 of 30
19
MGU Computer Science Series  ·  Programming with Python  ·  Library Textbook Edition

Chapter 19:
Nested Data
Structures

Lists inside lists, dictionaries inside lists, lists inside dictionaries — how Python represents real, structured information, and how to read it one level at a time

■ Part IV of VII ■

■ 10 Sections  •  Chapter 19 of 30 ■

Builds on Chapters 16–18 — strings, lists, tuples, sets, and dictionaries

Chapter Introduction and Learning Objectives

So far, each collection you have worked with has held simple values: a list of numbers, a dictionary mapping names to scores, a set of unique words. But real programs rarely deal with data this flat. A gradebook does not just hold a list of names. It holds names alongside scores, assignments, and averages. A contact book does not just hold phone numbers. It holds records with names, numbers, addresses, and notes. A simple database does not hold single values. It holds rows, each of which has multiple fields.

Python lets you build these richer structures by putting collections inside other collections. A list can contain other lists. A dictionary can map keys to lists. A list can hold dictionaries. These are called nested data structures.

Why This Chapter Matters

This chapter teaches you how to build nested structures, read them, update them, loop through them, and reason about them carefully. Nested structures are powerful, but they also require patience. A small confusion about which level you are working at can produce bugs that are hard to trace. You will learn to think in levels, to draw diagrams, and to write code that handles nested data clearly.

10Sections
9Objectives
10Key Terms
30Total Chapters

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Explain what a nested data structure is and why flat collections are often not enough.
  2. 2Create, read, and update nested lists, including two-dimensional lists representing tables.
  3. 3Build and process a list of dictionaries to represent a collection of records.
  4. 4Build and process a dictionary of lists, including using setdefault() to avoid key errors.
  5. 5Define a record and construct records that contain nested lists and nested dictionaries.
  6. 6Write and trace nested loops that process multi-level data structures.
  7. 7Read deeply nested expressions one bracket pair at a time and diagram nested structures.
  8. 8Design an appropriate data shape for a problem before writing code.
  9. 9Identify and correct common errors involving nested lists and dictionaries.

Sections at a Glance

Sections 19.1 – 19.3
Building Blocks: Why Nest Data, and Nested Lists as Tables
Combining Collections · Two-Index Access · Rows and Columns

Data Inside Data

Think about how you might describe a single student in a gradebook. You need a name. You need a list of quiz scores. You might need an average. You might need a letter grade. No single Python type holds all of that naturally. A string holds the name. A list holds the scores. A float holds the average. A dictionary, however, can hold all of these pieces together under one label.

student = {
    "name": "Amara",
    "scores": [88, 92, 79, 95],
    "average": 88.5,
    "grade": "B+"
}

Here, the dictionary student contains a list as the value for the key "scores". That list is nested inside the dictionary.

Now imagine you have thirty students. You could put each student dictionary into a list.

gradebook = [
    {"name": "Amara",  "scores": [88, 92, 79, 95]},
    {"name": "Ben",    "scores": [74, 81, 68, 72]},
    {"name": "Celia",  "scores": [95, 99, 97, 100]}
]

This is a list of dictionaries. The outer structure is a list. Each item inside is a dictionary. Each dictionary contains a list.

Three levels. All standard Python. All readable. Nested data structures are not a new feature. They are just collections used as values inside other collections. You already know lists and dictionaries. This chapter teaches you to use them together.

Why Nesting Matters

Flat data becomes limiting quickly. Consider storing the names of students who passed a course. A list works fine.

passed = ["Amara", "Celia", "Devon"]

Now consider storing each student's name and their final score together. A flat list cannot do this cleanly.

# This is confusing and fragile
passed = ["Amara", 91.0, "Celia", 98.0, "Devon", 84.5]

A list of dictionaries expresses the same information far more clearly.

passed = [
    {"name": "Amara", "final": 91.0},
    {"name": "Celia", "final": 98.0},
    {"name": "Devon", "final": 84.5}
]
Study Note

Any time you find yourself storing related pieces of data alongside each other in a flat list, that is usually a signal that nesting is a better choice.

Nested Lists

A nested list is a list that contains other lists as its elements.

rows = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

Here, rows is a list with three elements. Each element is itself a list with three integers. To access the outer list:

rows[0]    # [1, 2, 3]
rows[1]    # [4, 5, 6]
rows[2]    # [7, 8, 9]

To access an element inside an inner list, use two indexes. The first index selects the inner list. The second index selects the element within it.

rows[0][0]    # 1
rows[0][2]    # 3
rows[1][1]    # 5
rows[2][0]    # 7

Read rows[1][1] as: go to rows, take item at index 1 (which is [4, 5, 6]), then take the item at index 1 from that list (which is 5).

Tracing Nested List Access

Trace this step by step.

data = [
    [10, 20, 30],
    [40, 50, 60]
]

x = data[0][2]
y = data[1][0]
ExpressionStep 1Step 2Result
data[0][2]data[0] → [10, 20, 30][10, 20, 30][2]30
data[1][0]data[1] → [40, 50, 60][40, 50, 60][0]40

So x is 30 and y is 40.

Modifying a Nested List

You can update elements at any level using the same two-index syntax.

data = [[1, 2], [3, 4]]
data[0][1] = 99
print(data)    # [[1, 99], [3, 4]]

You can also append to an inner list.

data[1].append(5)
print(data)    # [[1, 99], [3, 4, 5]]

Tables as Lists of Lists

A two-dimensional list — a list of lists where every inner list has the same length — is a natural way to represent a table.

scores = [
    [85, 90, 78],
    [92, 88, 95],
    [70, 74, 81]
]

Think of this as three rows and three columns. Each inner list is one row. Each index position within a row is one column.

Row IndexColumn 0Column 1Column 2
0859078
1928895
2707481

To get the value in row 1, column 2:

scores[1][2]    # 95

Printing a Table Neatly

A nested loop over the rows and columns lets you print a table:

scores = [
    [85, 90, 78],
    [92, 88, 95],
    [70, 74, 81]
]

for row in scores:
    for value in row:
        print(value, end="\t")
    print()
Output
85	90	78
92	88	95
70	74	81

The end="\t" argument prints a tab instead of a newline after each number. The bare print() at the end of each row starts a new line.

Calculating a Row Total

scores = [
    [85, 90, 78],
    [92, 88, 95],
    [70, 74, 81]
]

for i, row in enumerate(scores):
    total = sum(row)
    average = total / len(row)
    print(f"Student {i}: total = {total}, average = {average:.1f}")
Output
Student 0: total = 253, average = 84.3
Student 1: total = 275, average = 91.7
Student 2: total = 225, average = 75.0
Why This Works

sum(row) works because each row is an ordinary list of numbers. Nesting does not break the tools you already know.

Sections 19.4 – 19.6
Records: Lists of Dictionaries, Dictionaries of Lists, and Records
Storing Real Entities · Multi-Valued Keys · setdefault()

Lists of Dictionaries

A list of dictionaries stores a sequence of records, where each record is a dictionary.

students = [
    {"name": "Amara",  "grade": "A", "score": 95},
    {"name": "Ben",    "grade": "C", "score": 72},
    {"name": "Celia",  "grade": "A", "score": 98}
]

Each item in students is a dictionary with the same three keys. The list holds them in order.

Accessing a Record

students[0]               # {"name": "Amara", "grade": "A", "score": 95}
students[0]["name"]       # "Amara"
students[1]["score"]      # 72
students[2]["grade"]      # "A"

Read students[1]["score"] as: go to students, take item at index 1 (Ben's record), then look up key "score" in that dictionary (which is 72).

Looping Through a List of Dictionaries

for student in students:
    print(f"{student['name']}: {student['grade']} ({student['score']})")
Output
Amara: A (95)
Ben: C (72)
Celia: A (98)

Each iteration gives you one dictionary. You can access any key inside that dictionary normally.

Finding a Specific Record

def find_student(students, name):
    for student in students:
        if student["name"] == name:
            return student
    return None

result = find_student(students, "Ben")
if result:
    print(result["score"])    # 72

Adding and Updating Records

# Add a new student
students.append({"name": "Devon", "grade": "B", "score": 84})

# Update an existing student's score
students[1]["score"] = 75
students[1]["grade"] = "C+"

Dictionaries of Lists

A dictionary of lists maps each key to a list of values. This structure is useful when one key is associated with multiple values — for example, a course roster where each teacher is associated with a list of students, or a schedule where each day has a list of events.

roster = {
    "Monday":    ["Intro Python", "Web Dev", "Data Structures"],
    "Wednesday": ["Intro Python", "Algorithms"],
    "Friday":    ["Web Dev", "Databases", "Capstone Lab"]
}

Accessing a List Inside a Dictionary

roster["Monday"]         # ["Intro Python", "Web Dev", "Data Structures"]
roster["Monday"][0]      # "Intro Python"
roster["Friday"][2]      # "Capstone Lab"

Looping Through a Dictionary of Lists

for day, courses in roster.items():
    print(f"{day}:")
    for course in courses:
        print(f"  - {course}")
Output
Monday:
  - Intro Python
  - Web Dev
  - Data Structures
Wednesday:
  - Intro Python
  - Algorithms
Friday:
  - Web Dev
  - Databases
  - Capstone Lab

Adding to a List Inside a Dictionary

roster["Wednesday"].append("Office Hours")

If a key might not exist yet, use setdefault() to create an empty list first.

assignments = {}
assignments.setdefault("Amara", []).append("Lab 1")
assignments.setdefault("Amara", []).append("Lab 2")
assignments.setdefault("Ben",   []).append("Lab 1")

print(assignments)
# {"Amara": ["Lab 1", "Lab 2"], "Ben": ["Lab 1"]}
How setdefault() Works

setdefault(key, default) returns the existing value if the key exists, or sets the key to default and returns it. This avoids a KeyError when appending to a key that does not yet exist.

Records

A record is a dictionary that represents a single real-world object or entity, with each key describing one attribute of that object.

book = {
    "title":   "The Hitchhiker's Guide to the Galaxy",
    "author":  "Douglas Adams",
    "year":    1979,
    "pages":   193,
    "in_stock": True
}

Records are one of the most practical patterns in Python. They let you group related data under a single name without defining a class.

Records With Nested Values

A record's values can themselves be collections.

employee = {
    "name":       "Rosa Ferreira",
    "department": "Engineering",
    "skills":     ["Python", "SQL", "Linux"],
    "contact": {
        "email": "[email protected]",
        "phone": "555-0184"
    }
}

Here, "skills" maps to a list and "contact" maps to another dictionary. Accessing nested values:

employee["name"]                   # "Rosa Ferreira"
employee["skills"][0]              # "Python"
employee["contact"]["email"]       # "[email protected]"

Each step drills one level deeper.

Records in a List

In practice, you usually have a list of records.

employees = [
    {
        "name":       "Rosa Ferreira",
        "department": "Engineering",
        "skills":     ["Python", "SQL", "Linux"]
    },
    {
        "name":       "James Okafor",
        "department": "Marketing",
        "skills":     ["Excel", "Analytics", "Copywriting"]
    }
]

for emp in employees:
    skill_list = ", ".join(emp["skills"])
    print(f"{emp['name']} ({emp['department']}): {skill_list}")
Output
Rosa Ferreira (Engineering): Python, SQL, Linux
James Okafor (Marketing): Excel, Analytics, Copywriting
Sections 19.7 – 19.10
Working With Nested Data: Loops, Reading, Design, and Avoiding Confusion
Loop-in-Loop Tracing · Box Diagrams · Data Shape Design · Common Errors

Nested Loops

When you loop through a nested structure, you often need a loop inside a loop. The outer loop iterates over the outer collection. The inner loop iterates over each inner collection.

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for row in matrix:
    for value in row:
        print(value, end=" ")
    print()
Output
1 2 3
4 5 6
7 8 9

Tracing a Nested Loop

Trace this carefully.

data = [[10, 20], [30, 40], [50, 60]]
total = 0

for row in data:
    for value in row:
        total += value

print(total)
Outer Loop: rowInner Loop: valuetotal After Step
[10, 20]1010
[10, 20]2030
[30, 40]3060
[30, 40]40100
[50, 60]50150
[50, 60]60210

Final output: 210.

Nested Loop With a List of Dictionaries

courses = [
    {"title": "Python 101",  "students": ["Amara", "Ben", "Celia"]},
    {"title": "Python 201",  "students": ["Devon", "Elena"]}
]

for course in courses:
    print(f"Course: {course['title']}")
    for student in course["students"]:
        print(f"  {student}")
Output
Course: Python 101
  Amara
  Ben
  Celia
Course: Python 201
  Devon
  Elena

The outer loop iterates over course records. The inner loop iterates over the list of students inside each record.

Reading Nested Structures Carefully

Nested structures can be confusing until you slow down and read them level by level. A useful technique: draw a box diagram before writing any code.

For a list of dictionaries:

catalog  →  list
              item 0  →  dict
                          "title"  →  "Python 101"
                          "price"  →  49.99
                          "tags"   →  list
                                       item 0  →  "beginner"
                                       item 1  →  "programming"
              item 1  →  dict
                          "title"  →  "Data Science Basics"
                          "price"  →  59.99
                          "tags"   →  list
                                       item 0  →  "data"
                                       item 1  →  "intermediate"

Given this structure, how would you print the first tag of the second course? Think step by step:

Worked Example
Drilling down one level at a time
  • Step 1catalog — the whole list
  • Step 2catalog[1] — the second dictionary
  • Step 3catalog[1]["tags"] — the list of tags
  • Step 4catalog[1]["tags"][0] — the first tag
catalog = [
    {"title": "Python 101",        "price": 49.99, "tags": ["beginner", "programming"]},
    {"title": "Data Science Basics","price": 59.99, "tags": ["data", "intermediate"]}
]

print(catalog[1]["tags"][0])    # "data"

The "One Level at a Time" Rule

When accessing deeply nested values, read the expression from left to right, one bracket pair at a time.

catalog[1]["tags"][0]

  • catalog — the list
  • [1] — take the second item (a dictionary)
  • ["tags"] — look up the "tags" key in that dictionary (a list)
  • [0] — take the first item of that list (a string)

If you get an error, check whether each step is giving you what you expect. Print intermediate values.

print(catalog[1])             # the dictionary
print(catalog[1]["tags"])     # the list
print(catalog[1]["tags"][0])  # the first tag
Debugging Habit

When you are not sure what a nested expression gives you, print it before using it. This takes thirty seconds and saves you from fifteen minutes of confusing errors.

Designing Data Shapes

Before writing nested data structure code, it helps to decide the shape of your data. Ask yourself:

  • What is the primary collection? A list (ordered sequence) or a dictionary (keyed lookup)?
  • What does each item look like? A single value? A record (dictionary)? A list of values?
  • Do any fields hold multiple values? If so, those fields need their own inner collections.
  • Will you need to look up items by key, or iterate through them in order?

Example: Designing a Phone Book

Requirements: store multiple contacts, each with a name, phone number, and list of labels. Start with the shape:

contacts  →  list of records
each record  →  dict with "name" (string), "phone" (string), "labels" (list of strings)

Code:

contacts = [
    {"name": "Amara Diallo",  "phone": "555-0100", "labels": ["friend", "classmate"]},
    {"name": "Ben Nakamura",  "phone": "555-0181", "labels": ["family"]},
    {"name": "Celia Reyes",   "phone": "555-0234", "labels": ["friend", "study group"]}
]

This design is clean. Each record is a dictionary. The "labels" field holds a list because one contact can have multiple labels.

Common Data Shapes in Python Programs

Use CaseOuter StructureEach Item
Sequence of simple valuesliststring, number
Sequence of recordslistdict
Key → one valuedictstring, number
Key → multiple valuesdictlist
Key → structured recorddictdict
Table (rows and columns)listlist

There is rarely one right answer. Choose the shape that makes your code readable and your access patterns natural.

Avoiding Confusing Nested Code

Nested structures are powerful. They can also produce code that is hard to read, hard to debug, and easy to get wrong. Here are guidelines for keeping nested code manageable.

Guideline 1: Use Named Variables for Intermediate Values

Instead of:

print(roster["Monday"][0])

Consider:

monday_courses = roster["Monday"]
first_course = monday_courses[0]
print(first_course)

This is longer, but each step is clear. When something goes wrong, you can print monday_courses and see exactly what you are working with.

Guideline 2: Use Helper Functions to Reduce Deep Nesting

Instead of embedding complex nested access inside other logic:

for student in gradebook:
    if student["scores"][2] > 90:
        print(student["name"])

Write a helper that makes the intent clear:

def third_score(student):
    return student["scores"][2]

for student in gradebook:
    if third_score(student) > 90:
        print(student["name"])

Guideline 3: Keep Nesting Depth Reasonable

Most programs need at most two or three levels of nesting. If you find yourself writing data[a][b][c][d], the data structure may need to be redesigned. A structure that goes four levels deep is not wrong by definition, but it is a warning sign to step back and ask whether there is a cleaner shape.

Guideline 4: Do Not Guess — Trace First

When you are not sure what a nested expression gives you, print it before using it.

print(type(students[0]))           # <class 'dict'>
print(students[0].keys())          # dict_keys(['name', 'grade', 'score'])
print(students[0]["name"])         # "Amara"

Common Errors With Nested Structures

Wrong number of indexes

matrix = [[1, 2], [3, 4]]
print(matrix[0])      # [1, 2]  — this is the inner list, not a value
print(matrix[0][1])   # 2       — this is a value
Using a list index on a dictionary

record = {"name": "Amara", "score": 95}
print(record[0])    # KeyError: 0
# A dictionary uses keys, not integer indexes
Using a dictionary key on a list

scores = [88, 92, 79]
print(scores["first"])    # TypeError
# A list uses integer indexes, not string keys
Appending to the wrong level

gradebook = [{"name": "Amara", "scores": [88, 92]}]
gradebook["scores"].append(79)    # TypeError: list indices must be integers
# You must access the dictionary first:
gradebook[0]["scores"].append(79)

Chapter Summary

Key Terms

nested data structure
A collection that contains other collections as its elements.
nested list
A list whose elements are themselves lists.
two-dimensional list
A list of lists where every inner list has the same length, used to represent a table with rows and columns.
list of dictionaries
A list in which each element is a dictionary, typically representing a record.
dictionary of lists
A dictionary in which each value is a list of items associated with that key.
record
A dictionary used to represent a single real-world object or entity, with each key describing one attribute.
nested loop
A loop that contains another loop in its body, used to process multi-level data structures.
inner list
The list stored inside another list in a nested structure.
outer list
The list that contains other lists or collections as its elements.
intermediate value
A named variable that holds a result partway through a multi-step access chain, used to make code clearer and easier to debug.
setdefault(key, default)
A dictionary method that returns the value for key if it exists, or sets key to default and returns it if it does not.

Concept Check

Answer these questions before beginning the full review questions. They check basic understanding, not deep analysis.

  1. In your own words, what is a nested data structure?
  2. What is the difference between data[0] and data[0][0] when data is a list of lists?
  3. What is a record, and what Python type is normally used to represent one?
  4. What is the difference between a list of dictionaries and a dictionary of lists? Give one example use case for each.
  5. What problem does setdefault() solve when building a dictionary of lists?
  6. Why is a box diagram useful before writing code for a nested structure?
  7. Name one guideline for keeping nested code readable.

Common Misconceptions

Misconception 1
"Nested data structures are a special, advanced feature of Python."
Nesting is not a new feature. It is simply using a list or dictionary as a value inside another list or dictionary. Everything you already know about indexing, looping, and modifying lists and dictionaries still applies — you are just applying it more than once in a row.
Misconception 2
"I can use a single index to reach any value inside a nested structure."
Each level of nesting requires its own index or key. To reach a value two levels deep, you need two bracket operations, such as data[0][2] or catalog[1]["tags"]. Skipping a level produces the wrong value or an error.
Misconception 3
"A list of dictionaries and a dictionary of lists are basically the same thing."
They organize data very differently. A list of dictionaries is an ordered sequence of records, each accessed by position and then by key. A dictionary of lists is a keyed lookup where each key maps directly to a list of values. Choosing the wrong one for a problem leads to awkward, hard-to-read code.
Misconception 4
"Deeper nesting always means a better, more complete data design."
Deep nesting often makes code harder to read, harder to debug, and more error-prone. Most programs need at most two or three levels. If you find yourself writing four or more bracket operations in a row, it is usually a sign to redesign the data shape rather than push forward.
PracticeCode Reading

Read each code block and answer the questions without running the code.

Exercise 1

data = [
    [5, 10, 15],
    [20, 25, 30],
    [35, 40, 45]
]
  1. What is data[0]?
  2. What is data[2][1]?
  3. What is data[1][0]?
  4. What is len(data)?
  5. What is len(data[0])?

Exercise 2

library = [
    {"title": "Dune",          "author": "Herbert", "year": 1965},
    {"title": "Neuromancer",   "author": "Gibson",  "year": 1984},
    {"title": "Ender's Game",  "author": "Card",    "year": 1985}
]
  1. What does library[1] return?
  2. What does library[0]["author"] return?
  3. What does library[2]["year"] return?
  4. How would you access the title "Neuromancer" using indexes and keys?
  5. What does len(library) return?

Exercise 3

schedule = {
    "Mon": ["Math", "Science", "English"],
    "Wed": ["History", "PE"],
    "Fri": ["Math", "Art", "Science"]
}
  1. What does schedule["Mon"] return?
  2. What does schedule["Fri"][1] return?
  3. What does len(schedule["Wed"]) return?
  4. How would you get the string "Science" from the "Fri" list?

Exercise 4

person = {
    "name": "Jordan Kim",
    "age": 22,
    "hobbies": ["hiking", "reading", "coding"],
    "address": {
        "city": "Portland",
        "state": "OR"
    }
}
  1. What does person["hobbies"][2] return?
  2. What does person["address"]["city"] return?
  3. What does len(person["hobbies"]) return?
PracticeCode Tracing

Trace each program by hand and write the output.

Exercise 5

rows = [[1, 2], [3, 4], [5, 6]]

for row in rows:
    print(row[0] + row[1])

Exercise 6

records = [
    {"name": "Ana",   "score": 88},
    {"name": "Bo",    "score": 74},
    {"name": "Cara",  "score": 95}
]

for r in records:
    if r["score"] >= 90:
        print(r["name"])

Exercise 7

totals = {}

scores = [
    {"subject": "Math",    "points": 85},
    {"subject": "Science", "points": 90},
    {"subject": "Math",    "points": 92},
    {"subject": "Science", "points": 88}
]

for item in scores:
    subject = item["subject"]
    totals.setdefault(subject, 0)
    totals[subject] += item["points"]

for subject, total in totals.items():
    print(f"{subject}: {total}")

Exercise 8

matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
count = 0

for row in matrix:
    for val in row:
        if val == 1:
            count += 1

print(count)
PracticePredict the Output

Exercise 9

data = {"fruits": ["apple", "banana", "cherry"]}
data["fruits"].append("date")
print(data["fruits"][3])

Exercise 10

students = [
    {"name": "Priya", "passed": True},
    {"name": "Sam",   "passed": False},
    {"name": "Tess",  "passed": True}
]

passed_names = []
for s in students:
    if s["passed"]:
        passed_names.append(s["name"])

print(passed_names)

Exercise 11

grid = [[0] * 3 for _ in range(3)]
grid[1][1] = 9
print(grid)

Exercise 12

groups = {
    "A": ["Alice", "Ahmed"],
    "B": ["Bilal"]
}

groups["A"].append("Anika")
groups["C"] = ["Carlos"]

for group, members in groups.items():
    print(f"{group}: {len(members)}")
PracticeDebugging and Error Analysis

Each example below contains a bug. Identify the error, explain what is wrong, and write the corrected code.

Exercise 13

records = [
    {"name": "Mia", "score": 88},
    {"name": "Leo", "score": 74}
]

for record in records:
    print(records["name"])    # Bug is here

Exercise 14

catalog = [
    {"title": "Python 101", "tags": ["beginner", "coding"]},
    {"title": "Data Basics", "tags": ["data", "intermediate"]}
]

first_tag = catalog["tags"][0]    # Bug is here
print(first_tag)

Exercise 15

schedule = {
    "Monday": ["Math", "Science"],
    "Tuesday": ["English", "Art"]
}

schedule["Monday"].append["History"]    # Bug is here

Exercise 16

table = [[1, 2, 3], [4, 5, 6]]

for row in table:
    for value in table:    # Bug is here
        print(value)
PracticeShort Programming Tasks
  1. Task 1. Create a list of three dictionaries representing movies. Each dictionary should have keys "title", "year", and "rating". Write a loop that prints only the movies with a rating above 8.0.
  2. Task 2. Create a dictionary named teams where each key is a team name and each value is a list of player names. Add at least two teams with at least three players each. Write a loop that prints each team name followed by how many players it has.
  3. Task 3. Write a function called row_averages(table) that takes a two-dimensional list of numbers and returns a list of the average of each row. For example, row_averages([[80, 90], [70, 60]]) should return [85.0, 65.0].
  4. Task 4. Given a list of student records (each a dictionary with "name" and "score"), write a function top_scorer(students) that returns the name of the student with the highest score.
  5. Task 5. Create a dictionary where each key is a student name and each value is a list of assignment scores. Write code that prints each student's name and their average score, formatted to one decimal place.
  6. Task 6. Write a function count_by_grade(students) that takes a list of student dictionaries (each with a "grade" key) and returns a dictionary counting how many students received each grade. For example, if three students got "A" and two got "B", the function returns {"A": 3, "B": 2}.
Mini-ProjectSimple Gradebook

Build a gradebook program that stores student data in a list of dictionaries and supports the following operations through a menu.

Requirements

Each student record should be a dictionary with:

  • "name" — a string
  • "scores" — a list of numeric scores

The program should offer a menu with these options:

  • 1. Add a new student (with an empty scores list)
  • 2. Add a score to an existing student
  • 3. Print all students and their average score
  • 4. Print the student with the highest average
  • 5. Quit

Sample Interaction

Gradebook Menu
1. Add student
2. Add score
3. Show all averages
4. Show top student
5. Quit
Choice: 1
Student name: Amara
Amara added.

Choice: 2
Student name: Amara
Score: 88
Score added.

Choice: 2
Student name: Amara
Score: 95
Score added.

Choice: 3
Amara: average = 91.5

Choice: 4
Top student: Amara (91.5)

Starter Structure

gradebook = []

def add_student(gradebook, name):
    # append a new record to gradebook
    pass

def add_score(gradebook, name, score):
    # find the student by name and append the score
    pass

def student_average(student):
    # return the average of student["scores"]
    # return 0 if there are no scores
    pass

def show_all_averages(gradebook):
    # print name and average for each student
    pass

def show_top_student(gradebook):
    # find and print the student with the highest average
    pass

Extension Tasks

  • Allow the user to search for a student by name and print their individual scores.
  • Prevent duplicate names when adding a new student.
  • Format averages to exactly one decimal place.
  • Print a warning if a student has no scores when calculating the average.
CumulativeMixed Review

These questions combine Chapter 19 material with earlier topics.

  1. Explain the difference between students[0] and students[0]["name"] if students is a list of dictionaries.
  2. A two-dimensional list has 4 rows and 3 columns. What is the valid range of row indexes? What is the valid range of column indexes?
  3. You have a dictionary of lists. Write code to check whether a specific value exists in any of the lists. (Hint: iterate through the values and use in.)
  4. Write a list comprehension that produces a list of names from a list of student dictionaries.
    students = [
        {"name": "Ana",  "score": 88},
        {"name": "Bo",   "score": 74},
        {"name": "Cara", "score": 95}
    ]
    names = [...]    # fill in the comprehension
  5. What is the output of this code?
    d = {}
    words = ["cat", "dog", "cat", "bird", "dog", "cat"]
    for word in words:
        d.setdefault(word, 0)
        d[word] += 1
    print(d)
  6. Rewrite this nested access so it uses two named intermediate variables.
    print(company["departments"][0]["manager"])
Chapter 19 Checklist
Use this to confirm your understanding before moving to Chapter 20
Concepts
  • I can explain what a nested data structure is and why flat collections are sometimes not enough.
  • I can create and access a nested list using two index positions.
  • I can represent and process a table as a two-dimensional list.
  • I can build, search, and update a list of dictionaries.
  • I can build a dictionary of lists and use setdefault() safely.
  • I can define a record and create records containing nested lists or dictionaries.
  • I can write and trace a nested loop over a multi-level structure.
Applications
  • I can read a deeply nested expression one bracket pair at a time.
  • I can draw a box diagram for a nested structure before writing code.
  • I can design an appropriate data shape — list, dictionary, or a combination — for a given problem.
  • I can identify and fix common nested-structure errors such as wrong index types and appending at the wrong level.
Study Habits
  • I use named intermediate variables when an access chain becomes hard to read.
  • I print intermediate values when I am unsure what a nested expression returns.
  • I keep nesting depth reasonable and reconsider the data shape when it grows too deep.
Preview — Chapter 20
Choosing and Combining Data Structures

Chapter 19 gave you the tools to build and navigate nested data. Chapter 20 steps back to ask a harder question: given a problem, which data structure — or combination of structures — should you choose? You have now worked with lists, dictionaries, sets, tuples, nested lists, lists of dictionaries, and dictionaries of lists. Each has strengths and tradeoffs. In Chapter 20, you will learn how to reason about order, uniqueness, lookup speed, mutability, and readability when choosing the right shape for your data. You will also practice designing data structures before writing any code — a habit that pays off in every larger program you build.

MGU Library Connection
  • Programming with Python — Appendix H: Data Structure Reference
  • Programming with Python — Appendix C: Python Syntax Quick Reference
  • Companion resource: Dictionary Methods Chart
  • Companion resource: List Methods Chart
  • Companion resource: Data Structure Selection Guide
  • Next chapter: Chapter 20 — Choosing and Combining Data Structures