Quiz 2
Pratiksha Dutta ID: 2330506
Question 1)
Given a dictionary student_scores where keys are student names and values are their scores, write code to retrieve the score for the
student "Jacques". After writing the code, explain why this approach is appropriate for retrieving a single value from a dictionary.
Solution
score = student_scores.get("Jacques")
The .get() approach is appropriate because it can safely retrieve a single value from the dictionary and return None if the key does not
exist.
Question 2
Consider the following dictionary structure that stores student details:
students = {
"Ali": {"Math": 88, "Science": 90},
"Bob": {"Math": 76, "Science": 85},
"Jacques": {"Math": 92, "Science": 87}
}
Write a function that updates Bob's Science score to 89. Before you write the function, describe your approach to accessing and updating
values in nested dictionaries and explain why it's efficient. Also describe what would happen if Bob's entry was missing and how you would
handle it. Code it.
In [1]: students = {
"Ali": {"Math": 88, "Science": 90},
"Bob": {"Math": 76, "Science": 85},
"Jacques": {"Math": 92, "Science": 87}
}
Approach
For updating a value inside a nested dictionary we need to access the outer dictionary using the student name key, Bob in this case, and
then access the inner dictionary using the subject name key, which is Science.
This approach is efficient because dictionary functions has an average time complexity of O(1) as Python uses hash tables.
In [2]: def update_score(students):
students["Bob"]["Science"] = 89
update_score(students)
print(students)
{'Ali': {'Math': 88, 'Science': 90}, 'Bob': {'Math': 76, 'Science': 89}, 'Jacques': {'Math': 92, 'Science': 87}}
If bob entry was not present, it would give us a KeyError that bob is not present.
In [3]: students = {
"Ali": {"Math": 88, "Science": 90},
"Jacques": {"Math": 92, "Science": 87}
}
update_score(students)
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[3], line 6
1 students = {
2 "Ali": {"Math": 88, "Science": 90},
3 "Jacques": {"Math": 92, "Science": 87}
4 }
----> 6 update_score(students)
Cell In[2], line 2, in update_score(students)
1 def update_score(students):
----> 2 students["Bob"]["Science"] = 89
KeyError: 'Bob'
We can solve this by handling the error in the update_score function using an if check.
In [ ]: def update_score(students):
if "Bob" in students:
students["Bob"]["Science"] = 89
print(students)
else:
print("Entry missing in dictionary")
update_score(students)
Question 3)
Expand on the students’ dictionary from the previous question. Write a function to calculate the average Math score for all students. Before
you code, outline the steps your function will take and explain why each step is necessary. After that, assume there’s an error in the
calculation if a student is missing the Math score. Identify and explain where the error would occur and how you would fix it.
Solution
The outline of the steps are as follows
1. We will initialize a total score and count variable to store the total score and number of students
These variables are needed to calculate the average score.
2. Loop through each key-value pair in the students dictionary:
This is needed to get each student’s Math score.
3. We will check if the math key exists in the dictionary:
We need to do this to avoid errors if a student does not have a Math score.
4. If a Math score is found, we will add it to the total_score and increase the count :
We need it for calculating the average.
5. We will calculate the average score by dividing total_score by count only if count is greater than 0:
We need this step to compute the average correctly .
In [ ]: def calculate_average(students):
total_score = 0
count = 0
for student, sub in students.items():
total_score += sub["Math"]
count += 1
if count > 0:
average = total_score / count
return average
else:
return "No Math scores available to calculate an average."
In [ ]: students = {
"Ali": {"Math": 88, "Science": 90},
"Bob": {"Math": 76, "Science": 89},
"Jacques": {"Math": 92, "Science": 87}
}
average = calculate_average(students)
print(f"Average Math Score: {average}")
if a student is missing the Math score, we get a KeyError
In [ ]: students = {
"Ali": {"Math": 88, "Science": 90},
"Bob": { "Science": 89},
"Jacques": {"Math": 92, "Science": 87}
}
average = calculate_average(students)
print(f"Average Math Score: {average}")
We can solve this by checking for the presence of the “Math” key before trying to access it and calculate the average. If not, print an error
emssage
In [ ]: def calculate_average(students):
total_score = 0
count = 0
for student, sub in students.items():
if "Math" in sub:
total_score += sub["Math"]
count += 1
else:
print(f"Error: {student} does not have a Math score.")
if count > 0:
average = total_score / count
return average
else:
return "No Math scores available to calculate an average."
students = {
"Ali": {"Math": 88, "Science": 90},
"Bob": { "Science": 89},
"Jacques": {"Math": 92, "Science": 87}
}
average = calculate_average(students)
print(f"Average Math Score: {average}")
Question 4)
Explain why tuples in Python are immutable. Then, using a tuple (3, 6, 9, 12), demonstrate an example where immutability would be
beneficial in a program. Also describe a situation where tuple immutability could create a limitation and explain how you would work around
it using other data structures.
Solution
Tuples are immutable because of following:
1. Since tuples cant be changed or modified throughout the program, we can use it as a data structure which holds data integrity
2. Tuples do not require to be modified, so it optimizes the storage making them more memory-efficient and faster to access compared
other data structures
In [ ]: intervals = (3, 6, 9, 12)
def display_reminder():
print("Reminders will be sent at the following hour intervals:")
for interval in intervals:
print(f"{interval} hours")
display_reminder()
Suppose the user wants to add another interval in the above example, he/she will not be able to do so because tuples are immutable. We
can work around it by using other data structures such as lists which is mutable to be able to edit it.
In [ ]: intervals = [3, 6, 9, 12]
def add_interval(interval):
if interval not in intervals:
intervals.append(interval)
def display_reminder():
print("Reminders will be sent at the following hour intervals:")
for interval in intervals:
print(f"{interval} hours")
add_interval(15)
display_reminder()
Question 5)
A dictionary portfolio contains investment names as keys and tuples as values. Each tuple holds (quantity, price_per_unit). For example:
portfolio = {
"Stock_A": (50, 200.75),
"Bond_B": (100, 98.5),
"Gold": (20, 1500.0)
}
Write a function that calculates the total value of each investment. Before coding, explain your approach, including how you handle each
tuple element.
Solution
The approach to the problem is as below:
1. A empty dictionary
2. We will first iterate each key-value pair in the dictionary.
3. We will then extract quantity and price_per_unit from the tuple.
4. The total value for that investment will be calculated and displayed
In [ ]: def calculate_value(portfolio):
totals = {}
for investment, (quantity, price_per_unit) in portfolio.items():
total = quantity * price_per_unit
totals[investment] = total
return totals
portfolio = {
"Stock_A": (50, 200.75),
"Bond_B": (100, 98.5),
"Gold": (20, 1500.0)
}
result = calculate_value(portfolio)
print(result)
Question 6)
You have a dictionary employee_ids where the keys are employee names and values are their IDs. Write code to check if an employee
named "David" exists in the dictionary. Then, explain why dictionary lookups are generally faster than list searches for this purpose.
Discuss a situation where a list might be more efficient than a dictionary for similar data and why.
Solution
employee_ids = {
"Ali": 101,
"Siya": 102,
"Clara": 103,
"David": 104,
}
if employee_ids.get("David"):
print("David exists")
else:
print("David does not exist")
dictionary functions use a hash table for quick lookups with average time complexity O(1) while Lists require linear searches, which are
O(n) in complexity.
A situation where a list might be more efficient than dictionary for similar data is as follows :
In [ ]: tasks = ["Prepare ingredients", "Preheat oven", "Mix ingredients", "Bake for 20 minutes", "Let it cool"]
for task in tasks:
print("Task:", task)
In [ ]: tasks = {
5: "Let it cool",
1: "Prepare ingredients",
2: "Preheat oven",
3: "Mix ingredients",
4: "Bake for 20 minutes",
for key in sorted(tasks):
print("Task:", tasks[key])
In the example above, a list is preferable because it maintains the order of tasks naturally and is simpler and more efficient for sequential
access. Using a dictionary would add unnecessary complexity and memory overhead. Also, for a smaller set of data, list is more efficient.
Question 7)
Write a function that takes a dictionary as input where each key is a student name and the value is a tuple (age, score). The function should
validate that each tuple has two elements, where age is an integer and score is a float.
In [ ]: def validate_student_data(student_data):
for name, (age, score) in student_data.items():
if not (isinstance(age, int) and isinstance(score, float)):
return f"Invalid data for {name}: age should be an integer and score should be a float."
return "All data is valid."
students = {
"Aarav": (20, 85.5),
"Ishita": (22, 90.0),
"Rohan": (19, 78.3),
"Ananya": (21, 88.7),
"Krishna": (23, 92.5),
"Meera": (20, 80.4),
"Suhas": (18, 75.0),
"Priya": (22, 89.6)
}
print(validate_student_data(students))
Question 8)
Explain how dictionaries in Python use hash maps for quick data access. After your explanation, create a small example to demonstrate
why using a dictionary would be more efficient than using a list for storing and retrieving values based on unique keys.
Solution
After we create the dictionary, Python will use a built-in hash() function to compute a unique hash value for each key. This hash value is
then used to determine the index where the corresponding value will be stored. When needed for retrieval, Python rehashes the key and
accesses the value directly through this index. This helps in lookups, insertions, and deletions with an average time complexity of O(1).
In [ ]: car = {
"toyota": "orange",
"ford": "red",
"mercedes": "yellow",
"lexus": "green",
"gmc": "black"
}
print(car["mercedes"])
When using a dictionary, accessing the color of “mercedes” is very efficient with average O(1) time complexity due to the hash function.
While, if we stored the same data as a list of tuples and needed to search for “mercedes,” we would have O(n) time complexity as we need
to iterate through all of the list
In [ ]: car_list = [
("toyota", "orange"),
("ford", "red"),
("mercedes", "yellow"),
("lexus", "green"),
("gmc", "black")
]
color = None
for make, col in car_list:
if make == "mercedes":
color = col
break
print(color)
Question 9)
Imagine you have a dictionary weather_data where keys are dates (as strings) and values are tuples representing (high_temp, low_temp).
Write a function to calculate the average high temperature over a specified range of dates. Before you code, describe how you would
ensure the date range is valid and outline your approach to accessing and averaging the relevant data. Also, suppose a date in the range
has missing temperature data (e.g., (None, None)). Explain how you would handle this and why careful data handling is essential.
Solution
The outline of the steps are as follows :
1. We will ensure the start date is earlier than or equal to the end date. We will also check that both dates exist in the dictionary. If either
date is not present, we return the error by informing the user or return a default value.
2. We will iterate through the dates within the specified range and consider the valid values of high_temp. Then we calculate the average
of the valid high_temp values. If there are no valid temperatures, we will handle this case separately.
3. If the temperature data for a given date is (None, None), we skip this date.
Carefully handling missing data is essential because it maintains the integrity of our analysis and ensures that incomplete data does not
lead to errors.
In [ ]: from datetime import datetime
def calc_avg_high(weather_data, start_dt, end_dt):
start = datetime.strptime(start_dt, "%Y-%m-%d")
end = datetime.strptime(end_dt, "%Y-%m-%d")
if start > end:
return "Start date must be earlier than or equal to the end date."
highs = []
for dt, (high, low) in weather_data.items():
curr_dt = datetime.strptime(dt, "%Y-%m-%d")
if start <= curr_dt <= end and high is not None:
highs.append(high)
if not highs:
return "No valid temperature data available in the specified range."
avg_high = sum(highs) / len(highs)
return round(avg_high, 2)
In [ ]: weather_data = {
"2024-11-01": (68.5, 50.3),
"2024-11-02": (70.1, 52.0),
"2024-11-03": (None, None),
"2024-11-07": (74.5, 56.0),
"2024-11-08": (None, None),
"2024-11-09": (71.6, 53.2),
}
start_date = "2024-11-03"
end_date = "2024-11-09"
average_high = calc_avg_high(weather_data, start_date, end_date)
print("Average High Temperature from", start_date, "to", end_date, ":", average_high)
In [ ]: