Winter 2024-25 final exam. Closed note, closed computer, 105 minutes.
The exam was pretty challenging, with a median score of 135 out of 160.
Each of the following Python expressions evaluates to a value without error. Write the resulting Python value in the box below each expression.
>>> 25 // 10
>>> 74 % 5
>>> s = 'Winter' >>> s[3:] + s[0]
>>> d = {'beta': 3, 'gamma': 4, 'alpha': 5}
>>> sorted(d.keys())
One-Liners For each part, write a 1-line expression to compute the indicated value with: map() or sorted() or min() or max() or a comprehension. You do not need to call list() for these.
# a. Given a list of int values, compute a list where # each value is multiplied * 10 >>> nums = [2, -7, 20, 9] # yields: [20, -70, 200, 90]
# b. Given a list of (x, y, z) tuples of numbers. # write a sorted/lambda expression to order the tuples # in decreasing order by y >>> tuples = [(1, 2, 3), (3, 1, 5), (2, 3, 2)] # yields: [(2, 3, 2), (1, 2, 3), (3, 1, 5)]
a. Given an int n which is guaranteed to be in the range 0..10 (inclusive).
The result of the function should be a different string depending on n: (a) if n is between 0 and 4 inclusive, the result is 'low', (b) n between 5 and 9 inclusive, the result is 'med', (c) n is 10, the result is 'yay'
yay(4) -> 'low' yay(10) -> 'yay'
def yay(n):
b. Given a string s, return a string made of the
chars of s, except each alphabetic char has an at sign '@' added before it.
'a1$B2' -> '@a1$@B2'
def at_alpha(s):
c. Given a string s, return the int sum of all the digits in s. If there are no digits in s, the result should be 0.
'a3C92$' -> 14
def sum_digits(s):
d. Given two lists, a and b, which contain strings.
Return a new list containing the strings which are in a, omitting the strings which are in b, and omitting the strings which contain a lowercase 'e'.
a = ['x', 'egg', 'y', 'z', 'boba'] b = ['z', 'x', 'donut'] result -> ['y', 'boba']
def not_be(a, b):
a. Given a string s and a non-negative int n. If there is an alphabetic char at index n, then return the uppercase form of that char. If there is not an alphabetic char at n, then check for an alphabetic char at index n + 1, and return its uppercase form if it is present. In all other cases, return the empty string.
('abc', 1) -> 'B'
('a$c', 1) -> 'C'
('a$@', 1) -> ''
('abc', 3) -> ''
def string_n(s, n):
b. Given a string s. Find the first '#' in s.
If the second char after the '#' is a digit,
then return its value squared.
In all other cases return -1.
'ab#23x' -> 9 'ab#xy' -> -1 'ab#x' -> -1 'ab' -> -1
def hash_digit(s):
c. Use s.find() to find the first '$$$' in s,
and the first '$' after it.
If the '$$$' and '$' strings are not present, then return
the empty string.
Consider the substring between the dollars, e.g. the substring 'aa,bb,cc'
within 'xx$$$aa,bb,cc$yy'.
The substring may be separated into parts by commas ','.
If the substring contains 2 or more commas,
then return the 2nd substring, e.g. 'bb'.
If there are not enough commas, then return the substring
after the '$'.
'xx$$$aa,bb,cc$yy' -> 'bb' 'xx$$$aa,bb$yy' -> 'yy' 'xx$$$aaabb$yy' -> 'yy' 'xx$$$aa,bb,cc' -> ''
def dollars(s):
a. We'll say a char is "cryptic" if it is 'a' or 'b' or 'c' (not case-sensitive), or the char is a digit, or it is a dash '-'. All other chars are not cryptic. Write code for an is_cryptic(ch) function which returns True if the ch parameter is cryptic, and False otherwise.
def is_cryptic(ch):
b. We're working on a system that embeds cryptic substrings inside strings. The end of a cryptic substring is marked by a '!!' with a series of cryptic chars immediately before it. Find the first '!!' in s. Look for contiguous cryptic chars immediately before the '!!'. If there are one or more cryptic chars there, then return the substring of the cryptic chars and the '!!' together. In all other cases, return the empty string ''. Use the (a) helper function.
'cxxAbc-12!!xx' -> 'Abc-12!!' 'a!!bb' -> 'a!!' 'xxx!!xx' -> '' 'xxx' -> ''
def cryptic(s):
For this problem, you will work out the coordinates to draw 1 line.
The canvas contains two rectangles. The first rectangle is at the upper left of the canvas, with width w1, and height h1. The second rectangle is immediately below and to the right of the first, with width w2, and height h2. We have a number num which is in the range 0..123 inclusive. We want to draw a horizontal line starting at the upper left pixel of the the second rectangle, with its length extending rightward proportionate to num. When num is 123, the line should reach all the way to the rightmost pixel. When num is 0, the line starts and ends at the upper left pixel of the second rectangle.
For your expressions below, use w1, h1, w2, h2, num in your code as needed.
The start point of the line is fixed at the upper left pixel of the second rectangle. The end point of the line extends horizontally, depending on num.
a. What is the Python expression for the x value of the start point:
b. What is the Python expression for the y value of the start point:
c. The line extends over to its end point. What is the python expression for the x value of its end point?
We have a "names" dict for a computer game network, with a name key for each person in the network. The value for each key is a list of that person's friends. It is possible that a name in the friend list is not a key in the names dict.
names = {
'zoe': ['sal', 'arun'],
'sal': ['arun', 'phil'],
'arun': ['sal', 'bizz', 'buzz']
}
To compute the score for a person, look at all their friends. For each friend, measure the length of their friend list. Add up all these lengths to get the score.
Example 1 - 'zoe' - the friends are 'sal' and 'arun'. sal's length is 2, and arun's length is 3, so the score is 5.
Example 2 - 'sal' - the friends are 'arun' and 'phil'. arun's length is 3, and 'phil' is not in the names dict, so contributes nothing, so the total score is 3.
Write code for the name_score(names, name) function, which takes in the names dict and a name which is guaranteed to be in the dict. Compute and return the score for that name.
def name_score(names, name):
We have the following "stores" dict of Trader Joe's sales data for today:
stores = {
'palo alto': {'61-42': 12, '55-23': 77},
'los gatos': {'55-45': 6}
'menlo park': {'04-99': 1, '61-42': 5, '55-45': 4}
}
Each key like 'palo alto' represents one store. It's value is a nested dict with a key for each product id, '61-42', and a positive count of how many of that product have sold today, e.g. 12. Products that have not sold any today are not in the dict.
Suppose we are curious about how a product like '61-42' is selling at the various stores. Write code for a product_counts(stores, id) function that takes in the stores dict and a single product id, e.g. '61-42', and returns a result dict like the one shown below, with a key for every store where the product has sold, and its value is the product's count for that store. Here is the result with the above stores dict, and the product id '61-42':
{
'palo alto': 12,
'menlo park': 5
}
def product_counts(stores, id):
We are collecting data about the peak sound intensity measured at various locations across the USA. We have a "dates" dict like this:
dates = {
'2-1-2025': {234: 79.2, 101: 92.6},
...
}
The key is a date string. The value for each key is a nested dict where the key is a unique int location code, e.g. 234, which identifies a location in the USA. The value is the largest sound level measured at that location on that date.
Write code for an add_sound(dates, date, loc, sound) function where dates is the dates dict, date is a particular date like '2-1-2025', loc is an int location code like 234, and sound is a sound level number like 84.1. Add the data for this date/loc/sound into the dict. The dates dict is storing the maximum sound level for each date/loc, so only update the stored sound level in the dict if the parameter sound level is greater. For example with the dict shown above, adding date='2-1-2025', loc=234, sound=81.0 would update the sound to 81.0. However, doing the same but with sound=71.0, the 79.2 number in the dict would be left in place, since it is larger. In all cases, return the dates dict.
def add_sound(dates, date, loc, sound):
You can credit for each part of this problem independently. Suppose we are running a bike-share operation, and each day we have the following "day" dict to keep track of what's going on:
day = { 'date': '3-15-2025',
'trips': {
't4523': {'amount': 12.5, 'paid': 3467},
't9922': {'amount': 5.0}
},
'bikes': {'b123': 't4523', 'b301': 't9922', 'b200': 't1000'}
}
a. The 'trips' key is always present in the day, and its value is a nested dict. Each key in the trips dict is a unique trip-id, e.g. 't4523', and its value is a nested dict with an 'amount' key. If the trip has been paid, then that trip-id will be in the trips dict, and the nested dict will have a 'paid' key with a payment id. If the trip is not paid, then the 'paid' key will not be present, and the trip-id may not be in the trips dict also.
Write code for a function is_paid(day, trip_id) which takes the day dict, and a trip_id like 't4523'. Return True if the trip_id has been paid. In all other cases return False.
def is_paid(day, trip_id):
b. The 'bikes' key is always present in the day, and its value is a nested dict of bikes. Each bike in the dict has a bike-id key, e.g. 'b123', and its value is the trip_id of the bike's most recent trip. Write code for an unpaid_bikes(day) function which computes and returns a list of the bike ids where the bike's trip is unpaid. For the dict above, the result would be ['b301', 'b200'], as their corresponding trips are unpaid.
def unpaid_bikes(day):
We have a data file which collects counts of small earthquakes, organized by state and date. Each line in the file look like this:
ca,12-1-2024,27@tx,11-6-1999,5@nj,11-21-2022
The line is divided into groups by at-signs '@', and there is at least 1 group on each line. Each group is divided into parts by commas ',' — e.g. 'ca,12-1-2024,27' has a state 'ca', a date '12-1-2024', and a count 27. There are no extraneous commas or at-signs in the chars that make up the state, date, and count. The parts of the date are separated from each other by dashes '-'. In some cases, a group in the file does not have a count ('nj' example above), and then the effective count for that group should be interpreted as 1.
Write code to build a "years" dict which has a year string key for each year seen in the data (we are not using the month or day). The value for each year is a nested dict with a key for each state, and its value is the sum of all the counts seen for that year/state, like this:
years = {
'2024': {'ca': 346, 'tx': 22},
'2022': {'nj': 2, 'wa': 44, 'ak': 201},
...
}
a. Write code to read in all the lines from the given filename, building and returning the "years" dict. The basic file-reading code is provided.
def read_hosts(filename):
years = {}
with open(filename) as f:
for line in f:
line = line.strip()
# Reminder of years structure
years = {
'2024': {'ca': 346, 'tx': 22},
'2022': {'nj': 2, 'wa': 44, 'ak': 201},
...
}
b. Write code for a print_years() function that takes in the years dict produced by (a), and prints out all the years in sorted order, one per line. For each year, print out all its states in alphabetical order, each indented by one space and followed by its total count like this:
2000 az 32 ca 97 mi 9 ... 2024 ak 582 ca 331 ny 6
def print_years(years):
#### 1. Short Answer
25 // 10 -> 2
74 % 5 -> 4
s[3:] + s[0] -> 'terW'
['alpha', 'beta', 'gamma']
## one-liners
a.
[n * 10 for n in nums]
-or-
map(lambda n: n * 10, nums)
b.
sorted(tuples, key=lambda t: t[2], reverse=True)
#### 2. Warmup Functions
def yay(n):
if n < 5:
return 'low'
if n < 10:
return 'med'
return 'yay'
# Could layer on "else" logic, but the return for each
# case means the code below can rule the prev cases out.
def at_alpha(s):
result = ''
for ch in s:
if ch.isalpha():
result += '@'
result += ch
# Here adding '@', then ch separately
# Or could use "else"
return result
def sum_digits(s):
total = 0
for ch in s:
if ch.isdigit():
total += int(ch)
return total
def not_be(a, b):
result = []
# Look at elements in a, check
# conditions before appending
for s in a:
# Could use "and" to join tests
if s not in b:
if 'e' not in s:
result.append(s)
return result
#### 3. Strings
def string_n(s, n):
if n < len(s) and s[n].isalpha():
return s[n].upper()
if n + 1 < len(s) and s[n + 1].isalpha():
return s[n + 1].upper()
return ''
def hash_digit(s):
hash = s.find('#')
if hash == -1:
return -1
if hash + 2 < len(s) and s[hash + 2].isdigit():
return int(s[hash + 2]) ** 2
return -1
def dollars(s):
a = s.find('$$$')
b = s.find('$', a + 3)
if a == -1 or b == -1:
return ''
# Extract sub between dollars, use split()
sub = s[a + 3:b]
parts = sub.split(',')
if len(parts) >= 3: # i.e. 2 or more commas
return parts[1]
return s[b + 1:] # Otherwise string after '$'
#### 4. String-2 Loop
def is_cryptic(ch):
low = ch.lower()
# Here using a series of if - could use "or"
if low == 'a' or low == 'b' or low == 'c':
return True
if ch.isdigit() or ch == '-':
return True
return False
def cryptic(s):
end = s.find('!!')
if end == -1:
return ''
start = end - 1
while start >= 0 and is_cryptic(s[start]):
start -= 1
result = s[start + 1:end + 2]
if len(result) > 2: # Must have at least one cryptic char
return result
return ''
#### 5. Draw
a. w1
b. h1
c. w1 + (num / 123) * (w2 - 1)
#### 6. Dict-1
def name_score(names, name):
lst = names[name]
score = 0
for friend in lst:
if friend in names: # Must check in before [ ]
score += len(names[friend])
return score
#### 7. Dict-2
def product_count(stores, id):
result = {}
for store in stores.keys():
products = stores[store]
if id in products:
result[store] = products[id]
return result
#### 8. Dict-3
def add_sound(dates, date, loc, sound):
if date not in dates:
dates[date] = {}
locs = dates[date] # locs points to {234: ...}
if loc not in locs: # loc not seen, just set it
locs[loc] = sound
return dates
if sound > locs[loc]: # set sound if bigger
locs[loc] = sound
return dates
#### 9. Dict-4
def is_paid(day, trip_id):
trips = day['trips']
if trip_id in trips and 'paid' in trips[trip_id]:
return True
return False
def unpaid_bikes(day):
result = []
bikes = day['bikes']
for bike in bikes.keys():
trip = bikes[bike]
if not is_paid(day, trip):
result.append(bike)
return result
#### 10. Capstone
def capstone(filename):
# line like
# ca,12-1-2024,27@tx,11-6-1999,5@nj,11-21-2022
years = {}
with open(filename) as f:
for line in f:
line = line.strip()
groups = line.split('@')
for group in groups:
parts = group.split(',')
state = parts[0]
parts_date = parts[1].split('-') # split the date str
year = parts_date[2]
count = 1 # default value for count
if len(parts) > 2: # if count is there, use it
count = int(parts[2])
# now load up years dict
if year not in years:
years[year] = {}
states = years[year] # Var points to inner
if state not in states:
states[state] = 0
states[state] += count
return years
def print_years(years):
for year in sorted(years.keys()):
print(year)
states = years[year]
for state in sorted(states.keys()):
print(' ' + state, states[state])