Problem 1 · Python Foundations · 9 points
Summarize valid scores
Implement summarize(values) for a list of integers. Ignore scores outside 0..100; return (valid_count, valid_mean). Use None for the mean if no score is valid.
- A. Implement with a loop.
- B. Trace [-1,0,80,100,101].
- C. Test empty input and explain the zero-division guard.
Show worked solution and rubric
def summarize(values):
total = count = 0
for value in values:
if 0 <= value <= 100:
total += value
count += 1
return count, total / count if count else None
- A. Accumulate count and total only after the range check. Return a pair so callers can distinguish no data from a mean of zero.
- B. The valid scores are 0,80,100: count 3 and mean 60.0. A score of zero is valid.
- C. Empty input returns (0,None). The conditional divides only when count is positive; [0] instead returns (1,0.0).
Scoring rubric
- 3 points: Accumulate count and total only after the range check
- 3 points: The valid scores are 0,80,100: count 3 and mean 60.0
- 3 points: Empty input returns (0,None)
Review this unitProblem 2 · Functions and Program Design · 9 points
Validate a reusable price function
Implement total_price(prices, discount). prices is a list of nonnegative numeric prices; discount must be between 0 and 1 inclusive. Raise ValueError for a negative price or out-of-range discount. Return the discounted total without rounding or changing input.
- A. Implement validation and computation.
- B. Trace [10,20] with discount 0.25.
- C. Explain why validation belongs inside the reusable function and give boundary tests.
Show worked solution and rubric
def total_price(prices, discount):
if not 0 <= discount <= 1 or any(price < 0 for price in prices):
raise ValueError("invalid price or discount")
return sum(prices) * (1 - discount)
- A. Check all preconditions before returning sum(prices)*(1-discount). A generator used by any need not copy the list.
- B. The subtotal is 30 and multiplier 0.75, giving 22.5. The original prices remain [10,20].
- C. Callers can bypass a screen-level check, so the function protects its own contract. Test discount 0, discount 1, empty prices, negative price, and discount above 1.
Scoring rubric
- 3 points: Check all preconditions before returning sum(prices)*(1-discount)
- 3 points: The subtotal is 30 and multiplier 0.75, giving 22.5
- 3 points: Callers can bypass a screen-level check, so the function protects its own contract
Review this unitProblem 3 · Collections and Algorithms · 9 points
Count normalized words
Implement counts(words) for a list of strings. Trim surrounding whitespace, lowercase, discard empty strings, and return a frequency dictionary.
- A. Implement with a dictionary.
- B. Trace [" Cat ","cat","DOG",""] and explain normalization.
- C. Analyze complexity including total input text length L and unique normalized words U.
Show worked solution and rubric
def counts(words):
result = {}
for word in words:
key = word.strip().lower()
if key:
result[key] = result.get(key, 0) + 1
return result
- A. Normalize before the dictionary lookup and increment only nonempty keys. get(key,0) supplies a count for a new word.
- B. The dictionary is {"cat":2,"dog":1}. Case and surrounding whitespace should not split equivalent words.
- C. Expected time is O(L+N) for N entries, allowing empty strings. Stored key characters require O(K) space plus O(U) entries, where K is their total length.
Scoring rubric
- 3 points: Normalize before the dictionary lookup and increment only nonempty keys
- 3 points: The dictionary is {"cat":2,"dog":1}
- 3 points: Expected time is O(L+N) for N entries, allowing empty strings
Review this unitProblem 4 · Object-Oriented Python · 9 points
Avoid a shared mutable default
Implement Bag(items=None) with its own list, add(item), and snapshot() returning an independent list copy.
- A. Implement the class.
- B. Demonstrate that two default-constructed bags do not share items.
- C. Demonstrate protection from aliasing through the constructor and snapshot; explain why items=[] is risky.
Show worked solution and rubric
class Bag:
def __init__(self, items=None):
self._items = [] if items is None else list(items)
def add(self, item):
self._items.append(item)
def snapshot(self):
return list(self._items)
- A. Use None as the default and list(items) to copy provided data. Each call creates separate instance storage.
- B. After a=Bag(); b=Bag(); a.add("x"), b.snapshot() remains []. A mutable default list would be created once when the function is defined.
- C. Mutating the original constructor list or a returned snapshot must not change the Bag. A shallow copy is sufficient for the list container, but nested mutable elements are still shared.
Scoring rubric
- 3 points: Use None as the default and list(items) to copy provided data
- 3 points: After a=Bag(); b=Bag(); a.add("x"), b.snapshot() remains []
- 3 points: Mutating the original constructor list or a returned snapshot must not change the Bag
Review this unitProblem 5 · Files, APIs, and Automation · 9 points
Read a validated score file
Implement read_scores(path). Read UTF-8 text with one integer per nonblank line. Ignore blank lines; raise ValueError for malformed integers or values outside 0..100. Let file-access errors propagate.
- A. Implement with a context manager.
- B. Explain behavior for blank files and a line containing 105.
- C. Explain how to test this without a live API and why broad except: pass is harmful.
Show worked solution and rubric
def read_scores(path):
result = []
with open(path, encoding="utf-8") as source:
for line in source:
if not line.strip():
continue
value = int(line.strip())
if not 0 <= value <= 100:
raise ValueError("score out of range")
result.append(value)
return result
- A. Use with open so the file closes on success or failure. int conversion can naturally raise ValueError; range failures should explicitly raise it.
- B. A blank file returns []. A line 105 raises ValueError and no partial list is returned to the caller.
- C. Use temporary fixture files for valid, blank, malformed, and out-of-range cases. Silently skipping all failures could hide corruption or missing permissions.
Scoring rubric
- 3 points: Use with open so the file closes on success or failure
- 3 points: A blank file returns []
- 3 points: Use temporary fixture files for valid, blank, malformed, and out-of-range cases
Review this unitProblem 6 · Data Science With Python · 9 points
Compute a grouped mean without treating missing as zero
Given rows as (group,value) pairs, implement group_means(rows), ignoring values of None and excluding groups with no observed values. Other values are finite numbers. Use Python dictionaries, then describe the equivalent pandas operation.
- A. Implement grouped totals and counts.
- B. Trace [("A",10),("A",None),("A",20),("B",None),("C",0)].
- C. Explain why replacing missing values by zero changes the estimate and state the pandas equivalent.
Show worked solution and rubric
def group_means(rows):
totals, counts = {}, {}
for group, value in rows:
if value is not None:
totals[group] = totals.get(group, 0) + value
counts[group] = counts.get(group, 0) + 1
return {group: total / counts[group] for group, total in totals.items()}
- A. Maintain separate sums and counts only for observed values; divide each total by its observed count.
- B. The result is {"A":15.0,"C":0.0}. B is absent because it has no observed values. Zero for C is observed data.
- C. Filling A missing with zero would give 10 instead of 15. With columns group and value, drop missing values before groupby("group")["value"].mean(); document the missing-data assumption.
Scoring rubric
- 3 points: Maintain separate sums and counts only for observed values; divide each total by its observed count.
- 3 points: The result is {"A":15.0,"C":0.0}
- 3 points: Filling A missing with zero would give 10 instead of 15
Review this unitProblem 7 · Visualization and Communication · 9 points
Choose an honest plot
A class records mean study time of 20, 22, and 21 minutes on Monday, Tuesday, and Wednesday. Each mean comes from five volunteers.
- A. Write a matplotlib function returning a figure with a labeled bar chart and a zero baseline.
- B. Explain why a vertical axis starting at 19 exaggerates differences.
- C. State two limitations and one useful additional quantity to display.
Show worked solution and rubric
def study_chart():
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.bar(["Monday", "Tuesday", "Wednesday"], [20, 22, 21])
ax.set_ylabel("Mean study time (minutes)")
ax.set_title("Five volunteer responses per day")
ax.set_ylim(bottom=0)
return fig
- A. Use explicit day labels, minutes on the vertical axis, and ylim(bottom=0). Return the figure so the caller decides whether to save or display it.
- B. The difference 22-20 is only two minutes, or 10% of 20. Truncating the baseline makes the bars appear many times different in height.
- C. Five volunteers may not represent the class, and repeated days are not independent evidence of causation. Display sample sizes and variation, such as individual points or standard deviations.
Scoring rubric
- 3 points: Use explicit day labels, minutes on the vertical axis, and ylim(bottom=0)
- 3 points: The difference 22-20 is only two minutes, or 10% of 20
- 3 points: Five volunteers may not represent the class, and repeated days are not independent evidence of causation
Review this unit