Day 4: Dictionaries & Nested Data
Today’s focus: Phase 0, Chapter 4 of Python Foundations: dict creation, keys/values/items, combining lists and dicts, and nested dictionaries. Three challenges: a warm-up, an applied script, and a mini-project.
What I built
1. Contact Card (warm-up) — a nested dictionary of contacts, each holding their own phone/email details, with options to add a new key across all contacts, update a value, and remove a key:
print("Welcome to My Contact Card App")
# Define a nested dictionary of contacts.
# The outer keys are the contact names, and the values are nested dictionaries
# containing detail keys like 'phone' and 'email'.
contacts = {
"ralph": {
"phone": "12344312",
"email": "ralph@doe.com"
},
"lea": {
"phone": "43217869",
"email": "lea@doe.com"
}
}
# Function to display all contacts and their nested details
def print_contacts():
# Loop through the outer dictionary (contacts) to get each person's name and details
for name, contact_info in contacts.items():
print(f"\nName: {name}")
# Loop through the nested details dictionary for the current contact
for detail_key, detail_value in contact_info.items():
print(f"{detail_key} : {detail_value}")
# Display the initial list of contacts
print_contacts()
# Ask the user if they want to add a new detail key (e.g., 'birthday') to all contacts
add_choice = input("Do you want to add a new key value in the contact list (y/n?) ")
if add_choice.lower() == 'y':
key_name = input("Enter the name of the new key :\n")
# Loop through all contact names and initialize the new key with an empty string
for name in contacts:
contacts[name][key_name] = ''
# Display the contact list after adding the new key
print_contacts()
# Prompt user for which contact details they want to update
update_contact = input("Which contact you would like to update? ").lower()
# Check if the entered contact name exists in the dictionary
if update_contact in contacts:
# Prompt for the specific key to update and its new value
key_to_update = input("What key you like to update? :")
new_value = input("What value you would like to update? :")
# Update the nested dictionary value
contacts[update_contact][key_to_update] = new_value
else:
print("Contact not found!")
# Display the updated contact list
print_contacts()
# Ask the user if they want to remove a key-value pair from a contact's details
remove_choice = input("Do you want to remove any key value (y/n)? ")
if remove_choice.lower() == 'y':
# Prompt for the contact name (normalized to lowercase)
contact_name = input("Which contact's detail do you want to remove? ").lower()
# Verify the contact exists before proceeding
if contact_name in contacts:
key_to_remove = input("Which key would you like to remove? ")
# Check if the specific key exists in the nested contact dictionary before deleting
if key_to_remove in contacts[contact_name]:
# Delete the key-value pair using the 'del' statement
del contacts[contact_name][key_to_remove]
print(f"Removed '{key_to_remove}' from {contact_name}.")
else:
print(f"Key '{key_to_remove}' not found.")
else:
print("Contact not found.")
2. Inventory Lookup (applied) — a product/price dictionary with a single lookup, plus a running “shopping cart” loop that totals up purchases:
print("Welcome to my Inventory Lookup App")
# Define the inventory dictionary
products = {
"apple": 0.50,
"bread": 2.99,
"milk": 1.49,
"egg": 0.15,
"cheese": 4.50
}
# Function to print the available products and their prices
def print_products():
print("Available products:")
for product, price in products.items():
print(f"{product.title()}: ${price:.2f}")
print_products()
search_product = input("Enter a product name to look up its price: ").lower()
if search_product in products:
print(f"The price of {search_product.title()} is ${products[search_product]:.2f}")
else:
print(f"Sorry, {search_product.title()} is not in the inventory.")
# Ask the user if they want to buy something and keep track of the total cost
buy_choice = input("Would you like to buy something? (yes/no): ").lower()
cost_total = 0
# Loop to allow the user to buy multiple products until they choose to stop
while buy_choice == "yes":
product_to_buy = input("Enter the product name you want to buy: ").lower()
if product_to_buy in products:
quantity = int(input(f"How many {product_to_buy.title()} would you like to buy? "))
cost_total += products[product_to_buy] * quantity
print(f"Added {quantity} {product_to_buy.title()}(s) to your cart. Current total: ${cost_total:.2f}")
else:
print(f"Sorry, {product_to_buy.title()} is not in the inventory.")
buy_choice = input("Would you like to buy something else? (yes/no): ").lower()
print(f"Your total purchase amount is: ${cost_total:.2f}")
3. Student Roster (mini-project) — a list of dictionaries, each holding a list of grades, with the average computed and written back into the same dict, plus a manual high/low/class-average report:
print("Welcome to the Student Roster App")
students = [
{"name": "Alice", "grades": [85, 90, 78], "id": 1},
{"name": "Bob", "grades": [92, 88, 95], "id": 2},
{"name": "Charlie", "grades": [76, 82, 80], "id": 3}
]
# Calculate each student's average and store it in their dictionary
for student in students:
average = sum(student["grades"]) / len(student["grades"])
student["average"] = average
hi_stud_average=0
hi_stud_name=""
low_stud_average=0
low_stud_name=""
for student in students:
print(f"Student: {student['name']}, Average Grade: {student['average']:.2f}")
if hi_stud_average == 0 and low_stud_average == 0:
hi_stud_average = student["average"]
hi_stud_name = student["name"]
low_stud_average = student["average"]
low_stud_name = student["name"]
else:
if student["average"] > hi_stud_average:
hi_stud_average = student["average"]
hi_stud_name = student["name"]
if student["average"] < low_stud_average:
low_stud_average = student["average"]
low_stud_name = student["name"]
# Calculate the class-wide average across all students
class_average = sum(student["average"] for student in students) / len(students)
print(f"The student : {hi_stud_name} has the highest average of {hi_stud_average:.2f}")
print(f"The student : {low_stud_name} has the lowest average of {low_stud_average:.2f}")
print(f"The class-wide average across all students is: {class_average:.2f}")
Challenges I ran into
The real challenge on Day 4 wasn’t the dictionaries — it was pace. This one took me almost a week, compared to a day or so for each of the first three. The slowdown came from perfectionism: rewriting a working script because it didn’t feel “clean” enough, second-guessing variable names, stalling on a nested-dict structure before I’d even run it once to see if it worked. The thing that got me unstuck was letting go of the guilt around leaning on autocomplete/AI assistance while learning. I’d been treating it as cheating, like using it meant I hadn’t really learned the concept — but the same way a calculator doesn’t stop you from learning math, autocomplete doesn’t stop you from learning to structure a dictionary. The skill is in reading and understanding what it suggests, not in typing every character from scratch. Loosening that self-imposed rule is what got Day 4 across the finish line.
Once I was actually moving, the usual crop of bugs showed up:
- Contact Card’s “update” doesn’t check the key already exists.
contacts[update_contact][key_to_update] = new_valuewill happily create a brand-new key if I typo the one I meant to update — it should checkif key_to_update in contacts[update_contact]:first, the same way the remove flow already checks before deleting. - Inventory Lookup crashes on a non-numeric quantity.
quantity = int(input(...))has no guard around it, so typing “two” instead of “2” throws an unhandledValueErrorand kills the whole program. Needs atry/exceptaround the conversion. - The same sentinel-value bug from Day 3 came back. I flagged in the Day 3 post that seeding
score_hi/score_lowwith0was fragile — it’d break if a real value happened to be0. I used the exact same pattern again in the Student Roster (hi_stud_average = 0), and the check re-runs on every loop iteration, not just the first, so a student with a genuine0.0average would wrongly reset the tracking instead of comparing. Seeding fromstudents[0]["average"]before the loop starts is the fix — I clearly need to make this a habit rather than a one-off correction.
What I learned
Dictionaries click differently than lists did — instead of “where is this in the sequence,” it’s “what’s this called,” and .items() gives both at once instead of forcing an index lookup. Nesting a list inside a dict inside a list (Student Roster) isn’t a new concept so much as the same key:value and indexing rules applied one layer deeper, which was less intimidating once I actually tried it instead of overthinking the shape beforehand.
The bigger lesson was about process, not syntax: perfectionism was the actual bottleneck this week, not the material. And treating AI-assisted autocomplete as something to feel guilty about was slowing me down for no real reason — using it well is itself a skill worth building, not a shortcut around learning.
Next
Day 5 picks up JSON, pip, and virtual environments: the json module, installing packages, working inside a venv, and calling a public API for the first time.