Final 23W Key
Final 23W Key
Average score (arithmetic mean) was 68. Median was 70. There were lots of excellent
scores, and quite a few very low scores.
Histogram
40
35
30
25
Frequency
20
Frequency
15
10
0
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 More
Bin
1. [25 points] In a certain class, the average exam score for the term is calculated as the
arithmetic mean of exam scores, but up to two exams at which the student was not present
may be omitted. If the student missed three or more exams, those exams are included in
the average as zeroes. Exams at which the student was present are counted regardless of
score. Finish method average.
class Exam:
"""One exam score"""
def __init__(self, score: int, present: bool):
self.present = present
self.score = score
class TermExams:
"""Exam scores over a term"""
def __init__(self, scores: list[Exam]):
self.scores = scores
pretty_good = TermExams([
Exam(100, True), Exam(100, True),
Exam(0, False), Exam(100, True)])
assert pretty_good.average() == 100.0
pretty_bad = TermExams([
Exam(100, True), Exam(0, False),
Exam(0, False), Exam(0, False)])
assert pretty_bad.average() == 50.0
There were many ways to finish the average method correctly. My sample solution keeps
a separate count of exams to be averaged and total of those exams. Many students instead
kept a count of excused absences and subtracted that from the length of the ‘scores‘ list. I
think there were some other reasonable approaches that I don’t recall at the moment.
2. [25 points] Finish function no_columns_has_dups. You may write an additional function,
but do not transpose the matrix. It may be useful to remember that set() creates a set,
s.add(x) adds an element x to a set s, and x in s checks whether set s contains element
x.
assert no_column_has_dups(
[[1, 2, 2],
[2, 3, 3],
[3, 1, 1]])
assert no_column_has_dups([])
assert no_column_has_dups([[], []])
assert not no_column_has_dups(
[[1, 2, 3],
[8, 3, 1],
[7, 2, 8]])
Again there was more than one way to solve this problem. Note the necessity of testing
whether the grid is empty (len(grid) == 0) before determining the number of columns
(len(grid[0])).
One common approach that I had not anticipated was creating a set of elements in column
and then testing whether the set had the same length as the grid. That is also fine.
3. [25 points] Finish method count_good in the concrete classes Inner and Apple so that it
counts the number of apples in a tree that meet the quality standard of a Checker object.
class Checker:
"""Abstract base for class that accepts or rejects apples"""
def good(self, fruit: "Apple") -> bool:
raise NotImplementedError("Good method not defined")
class Tree:
"""Abstract base class for nodes of apple tree"""
def count_good(self, checker: Checker) -> int:
"""How many good applies in this tree?"""
raise NotImplementedError("count_good not defined")
class Inner(Tree):
def __init__(self, children: list[Tree]):
self.children = children
class Apple(Tree):
"""Leaf nodes of the apple tree"""
def __init__(self, sweetness: int, crispness: int, tartness: int):
self.sweetness = sweetness
self.crispness = crispness
self.tartness = tartness
class GoodForPie(Checker):
def good(self, fruit: "Apple") -> bool:
return fruit.crispness > 5 and fruit.tartness > 5
An important part of this problem was using a method of the Checker object passed to the
count_good method. Most students got it, but quite a few used GoodForPie directly even
though another Checker object of a different subclass could be passed.
4. [25 points] A run in a list is a sub-sequence of identical elements. For example, in the
list [“a”, “b”, “b”, “b”, “c”, “b”], there is a run of 3 “b”. It could be run-length encoded as
[(1, “a”), (3, “b”), (1, “c”), (1, “b”)].
Complete the add method of RunLengthEncoding so that as_pairs will return the results
indicated in the test cases below. Try to avoid repetitive code.
class RunLengthEncoding:
"""Instead of x x x y y x x x, encode as (3 x) (2 y) (3 x)"""
def __init__(self):
# Maintain items and repetitions as parallel arrays
self.items: list[str] = []
self.reps: list[int] = []
def as_pairs(self):
"""Provide as list of pairs"""
return [(self.reps[i], self.items[i]) for i in range(len(self.items))]
# (The 'zip' function also provides a concise way to do this)
rle = RunLengthEncoding()
rle.add_a_bunch(["a", "a", "x", "y", "y", "a", "a"])
assert rle.as_pairs() == [(2, "a"), (1, "x"), (2, "y"), (2, "a")]
rle.add_a_bunch(["a", "b", "b", "b"])
assert rle.as_pairs() == [(2, "a"), (1, "x"), (2, "y"), (3, "a"), (3, "b")]
In addition to list logic, this was a test of maintaining parallel array structures.
I was surprised by the number of students who seemed to mix up add_a_bunch (just a
quick way to call add several times) with add.