Closed note, closed computer, 105 minutes, 160 points.
The median score was 142/160 (89%) out of 160.
# CS106A Exam Reference Reminder # [square brackets] denote functions listed here that have been # mentioned but we have not used significantly. # Exam questions do not require such functions. General functions: len() int() str() float() range() list() abs() min() max() sum() sorted(lst, key=lambda, reverse=False) String functions: isalpha() isdigit() isupper() islower() find() upper() lower() split() strip() [replace()] [join()] startswith() endswith() List functions: append() index() map() [extend() pop()] Dict functions: keys() values() items() File functions with open(filename) as f: # with form for line in f: # loop over lines s = f.read() # read as one big string Lambda: lambda n: 2 * n Canvas/TK Draw # (The problem statement will mention any graphic functions # you need, so this list is just a reference.) canvas.draw_line(x1, y1, x2, y2) # x,y is upper left, optional color='red' parameter # float values are truncated to int automatically canvas.draw_rect(x, y, width, height) canvas.fill_rect(x, y, width, height) canvas.draw_oval(x, y, width, height) canvas.fill_oval(x, y, width, height) SimpleImage: # read filename image = SimpleImage(filename) # create blank image image = SimpleImage.blank(width, height) pixel = image.get_pixel(x, y) # access pixel by x,y pixel has .x .y .red .green .blue
Each of the following Python expressions evaluates to a value without error. Write the resulting Python value in the box below each expression.
>>> 107 // 25
>>> 26 % 10
>>> s = 'Spring' >>> s[0] + s[3:]
>>> d = {'beta': 3, 'gamma': 4, 'alpha': 5}
>>> # Want to produce: ['alpha', 'beta', 'gamma']
>>> # Write an expression to produce this from d
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 * -1 >>> nums = [3, 4, -20, 10] # yields: [-3, -4, 20, -10]
# b. Given a list of (x, y, z) tuples of numbers. # write am expression to extract the tuple # with the smallest y value >>> tuples = [(1, 2, 3), (3, 1, 5), (2, 3, 2)] # yields: (3, 1, 5)
a. Given an int n and a "lucky" list, return n * 10 if n is in the lucky list. Otherwise if n is not in the list but is greater than 19 (above teenager), then return n * 2. Otherwise return -1.
lucky_n(20, [10, 20, 30]) -> 200 lucky_n(20, [1, 2, 3]) -> 40 lucky_n(19, [1, 2, 3]) -> -1
def lucky_n(n, lucky):
b. Given a string s, return a string made of the alphabetic chars in s. In addition, each char added to the output which is uppercase should have a '!' added immediately after it. Solve with one or two loops.
'Xab12Y' -> 'X!abY!'
def alpha_hey(s):
c. Given an int n greater than 0, return a list of n ints [1, 2, .. n].
So for example n of 5 returns [1, 2, 3, 4, 5].
def n_ints(n):
a. Given a string s and a non-negative int n. Within s, if a 2 digit number starts at n, then return the value of that number doubled. (Use the 2 digits at n, not worrying about any later digits.) Otherwise, if there is just a 1 digit number at n, then return its numeric value. Otherwise return -1.
digits_n('x12x', 1) -> 24
digits_n('x123', 1) -> 24
digits_n('x1xx', 1) -> 1
digits_n('xxxx', 1) -> -1
digits_n('x', 1) -> -1
def digits_n(s, n):
b. Given a string s. Use s.find() to find the first set of double brackets '[[' in s, and
the matching ']]' after them. If both sets exist, then use slices and +
to create a new version of the string, where the '[[' becomes a '(',
and the ']]' becomes a ')'. If the sets are not present, return the
string unchanged. Do not use s.replace().
'ab[[hi!]]xy' -> 'ab(hi!)xy' def un_brackets(s):
We'll say a dot-com address in a text ends in '.com', with a series of zero or more "address" chars immediately before it. The address chars are either (1) alphabetic, (2) a dot '.', or (3) a dash '-'. Use s.find() to find the first '.com' in s, and then a while loop to figure the extent of the address chars before it. Return the whole dot-com address, so s 'x a.b-foo.com x' returns 'a.b-foo.com'. If there is no '.com', then return the empty string. If you need to write a long line of Python, you may continue it on the next line on the paper, and we will understand.
'xx www-test.foo.bar.com xx' -> 'www-test.foo.bar.com' '$.a-b-c.com$' -> '.a-b-c.com' 'a-b-c' -> '' def dot_com(s):
Given a "nums" list of ints. Consider a number n in nums. We'll say n has "flair" if the index it is at is also n. For example in the list [99, 13, 2, 5], the number 2 has flair since it is at index 2.
Write code to compute and return the first number with flair in the list, or -1 if no number has flair.
flair([99, 13, 2, 5]) -> 2 flair([99, 13, 1, 5]) -> -1 flair([3, 0, 0, 3]) -> 3 def flair(nums):
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. The second rectangle is divided into two parts side by side with widths w2 and w3, and both of height h2. We have a number num which is in the range 0..255 inclusive. We want to draw a horizontal line starting at the upper left pixel of the w2 rectangle, with its length extending rightward proportionate to num. When num is 255, the line should reach all the way to the upper right pixel of the w2 rectangle. When num is 0, the line starts and ends at the upper left pixel of the w2 rectangle.
For your expressions below, use w1, h1, w2, w3, h2, num in your code as needed.
The start point of the line is fixed at the upper left pixel of the w2 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 "years" dict, where each key is a year. Its value is a nested dict where the key is a dog's name, and the value is the count of dogs with that name.
years = {
2020: {'fido': 22, 'rover': 7, 'spot': 10},
2022: {'spot': 13, 'rover': 5, 'puddles': 96}
}
Write code for a year_dogs(years, year, lower_bound) function, where "years" is the years dict.
The function should construct and return a list of the dog names for the year identified by the "year" parameter, but only include the dog names where the count for that name is greater or equal to the "lower_bound" parameter.
For example with the data above, the year 2020 with lower_bound 10 returns: ['fido', 'spot']
def year_dogs(years, year, lower_bound):
Given a dictionry d, where the keys and values are lowercase strings. Consider the keys in alphabetical order. Construct and return a list of len-2 strings, one string for each key/value pair. Each string should be made of the first char of the key followed by the first char of the value. If a key or value does not have any chars, then omit the string for that key/value from the result.
# Example
d = {
'bee': '',
'apple': 'zebra',
'carrot': 'tea'
}
returns -> ['az', 'ct']
def first_chars(d):
We have a dict keeping track of the highest temperature seen in a zipcode, broken down by year, like this:
zips = {
94301: {2022: 102.1, 2021: 101.2, 2023: 104.0},
94025: {2021: 100.2, 2023: 97.4}
}
The "zips" dict has an int zipcode as its key. Its value is a nested dict with int year as its key, and its value is the float highest temperature seen for that zip/year.
Write code for an add_temp(zips, zip, year, temp) function where zips is the zips dict, zip is an int zipcode, year is an int year, and temp is a float temperature reading. Add the given data into the zips dict. Add zip and year keys to the structure as needed. Do not add the temperature if there is already a higher temperature for that zip/year combination, leaving the zips unchanged in that case. We'll assume there's a "return zips" line at the end of your function
# Examples, adding to the above zips (94301, 2022, 103.0) -> changes 102.1 to 103.0 (94301, 2023, 96.0) -> no change (94301, 2024, 96.0) -> adds 2024: 96.0 (94563, 2024, 97.0) -> adds 94563 zip, then 2024: 97.0 def add_temp(zips, zip, year, temp):
Suppose you are working on the code for a computer messaging system. We have a "system" dict which keeps track of the messages being handled, like this:
system = {
'messages': {
'm155': {'rcpt': 'r6473', 'acct': 34677},
'm200': {'rcpt': 'r4545'},
'm771': {'acct': 42},
'm399': {'rcpt': 'r1234'}
},
'logs': ['m155-r6473', 'm200-r4545', 'm771-r2257',
'm399-r5555', 'm600-r1122']
}
The system always has a 'messages' key, containing a nested dict which tracks information for each message. Each message has a key like 'm155', and its dict can contain optional 'acct' and 'rcpt' keys. The system also always has a 'logs' key which points to a list of log strings. Each log string is like 'm155-r6473' — the 'm155' is the key of a message, and 'r6473' is a receipt from the network, recording that the message has been sent.
a. Write a function no_account(system), which returns a list of the message key strings corresponding to messages that do not contain an 'acct' key, such as the 'm200' message in the example above. For the example above, the result would be the list ['m200', 'm399']
def no_acct(system):
For reference, here is the example system again
system = {
'messages': {
'm155': {'rcpt': 'r6473', 'acct': 34677},
'm200': {'rcpt': 'r4545'},
'm771': {'acct': 42},
'm399': {'rcpt': 'r1234'}
},
'logs': ['m155-r6473', 'm200-r4545', 'm771-r2257',
'm399-r5555', 'm600-r1122']
}
b. Consider a log like 'm155-r6473'. We'll say the log is "good" if the 'm155' in the log corresponds to a message key in the messages dict, and the nested dict for that message contains a 'rcpt' key, and its value matches the receipt in the log, e.g. 'r6473'. In other words, the log is good if the messages dict contains and matches the two pieces of information in the log string.
Write code for a function good_logs(system) which takes the system dict and returns a list of the good logs. For the example system, only the first 2 of the 5 log strings are good, and the result list would be: ['m155-r6473', 'm200-r4545']
def good_logs(system):
We are building a chat network, and each post created by a user is summarized by a text line that shows what sentiment the user has expressed about various tags. Each user can produce many lines, and a few lines from the file look like:
alice,candy:4,-mud:3,boba:23 aparna,tv:3,bike:2 alice,boba:3 ...
The line is divided into parts by commas. The first part is a user string, and the remaining parts contain a tag and a sentiment int separated by a colon. There may be a dash just before the tag string which indicates that the sentiment score should be treated as negative. The dash char is not part of the tag, and there are no extraneous commas, dashes, or colons in the user and tag strings. Write code to build a "tags" dict which has a tag string key for each tag seen in the data. The value for each tag is a nested dict with a key for each user, and its value is the sum of all the sentiment scores by that user about that tag, like this:
tags = { 'candy': {'alice': 7, 'bob': 3},
'mud': {'alice': -3, 'arun': 19, 'jose': -17},
...
a. Write code to read in all the lines from the given filename, building and returning the tags dict. The basic file-reading code is provided.
def read_tags(filename):
tags = {}
with open(filename) as f:
for line in f:
line = line.strip()
# Reminder of tags structure
tags = {
'candy': {'alice': 7, 'bob': 3},
'mud': {'alice': -3, 'arun': 19, 'jose': -17},
...
}
b. Write code for a print_tags() function that takes in the tags dict produced by (a), and prints out all the tags in sorted order, one per line. For each tag, print out all its users in alphabetical order, each indented by one space and followed by its total sentiment like this:
aardvarks alice 12 bob -4 zeke 14 bread alice 13 arun 23 ... zebras monica 2 zeke -13
def print_tags(tags):
### 1 Short Answer
4
6
'Sing'
sorted(d.keys()) # or sorted(d)
a. multiply each elem by -1
[n * -1 for n in nums]
map(lambda n: n * -1, nums)
b. Extract tuple with smallest y
min(tuples, key=lambda tup: tup[1])
### 2 Warmups
def lucky_n(n, lucky):
if n in lucky:
return n * 10
if n > 19:
return n * 2
return -1
def alpha_hey(s):
result = ''
for ch in s:
if ch.isalpha():
result += ch
if ch.isupper():
result += '!'
return result
# Want 1..n in list
# range(n) give us 0..n-1
def n_ints(n):
result = []
for i in range(n):
result.append(i + 1)
return result
### 3 String
def digits_n(s, n):
if n < len(s) and s[n].isdigit():
# If n and n+1 are both digits
if n + 1 < len(s) and s[n + 1].isdigit():
return int(s[n:n + 2]) * 2
# Otherwise just n
return int(s[n])
return -1
def un_brackets(s):
left = s.find('[[')
right = s.find(']]', left + 2) # works without the +2
if left != -1 and right != -1:
return s[:left] + '(' + s[left + 2:right] + ')' + s[right + 2:]
return s
### 4. String-2
def dot_com(s):
com = s.find('.com')
if com == -1:
return ''
# Move start leftwards over chars
start = com -1
while start >= 0 and (s[start].isalpha() or s[start] == '.' or s[start] == '-'):
start -= 1
return s[start + 1:com + 4]
### 5. List Flair
def flair(nums):
# Use for/i/range, so we have index number of each elem
for i in range(len(nums)):
if i == nums[i]:
return i
return -1
### 6. Draw
a. w1
b. h1
c. w1 + (num / 255) * (w2 - 1)
### 7. Dict-1
def dog_year(years, year, bound):
result = []
dogs = years[year] # Var points to dogs inner dict
# Loop over dict, check each key/value in it against bound
for dog in dogs.keys():
if dogs[dog] >= bound:
result.append(dog)
return result
### 8. Dict-2
def first_chars(d):
result = []
for key in sorted(d.keys()):
value = d[key]
if key != '' and value != '':
result.append(key[0] + value[0])
return result
### 9. Dict-3
def add_temp(zips, zip, year, temp):
if zip not in zips:
zips[zip] = {}
years = zips[zip] # Var points to inner
# Add temp if not in there, or if temp is larger
# Here using 2x if, or could use "or"
if year not in years:
years[year] = temp
elif temp > years[year]: # regular "if" works too
years[year] = temp
return zips
### 10 Dict-4
def no_acct(system):
result = []
messages = system['messages']
for key in messages.keys():
msg = messages[key]
if 'acct' not in msg:
result.append(key)
return result
def good_logs(system):
result = []
messages = system['messages']
logs = system['logs']
for log in logs:
parts = log.split('-')
id = parts[0]
rcpt = parts[1]
if id in messages:
msg = messages[id]
if 'rcpt' in msg and msg['rcpt'] == rcpt:
result.append(log)
return result
### 11. Capstone
def capstone(filename):
# line like
# bob,woot:4,-mud:2,cats:23
tags = {}
with open(filename) as f:
for line in f:
line = line.strip()
parts = line.split(',')
user = parts[0]
for part in parts[1:]: # slice to skip first
parts2 = part.split(':')
tag = parts2[0]
amt = int(parts2[1])
if tag[0] == '-': # '-' at start of tag
amt = amt * -1
tag = tag[1:] # trim off '-'
if tag not in tags:
tags[tag] = {}
users = tags[tag] # Var points to inner
if user not in users:
users[user] = 0
users[user] += amt
return tags
def print_tags(tags):
for tag in sorted(tags.keys()):
print(tag)
users = tags[tag]
for user in sorted(users.keys()):
print(' ' + user, users[user])