Write a function that reads a class roster, validates it, and writes a grade-distribution report.
generate_grade_report(in_path, out_path)
in_path contains one "name,score" pair per line (no header). For each line:
0 or above 100, raise ValueError(f"invalid score for {name}: {score}").90+ → "A", 80-89 → "B", 70-79 → "C", 60-69 → "D", below 60 → "F".Once every line is processed, build a report: one line per letter grade, in the order A, B, C, D, F, formatted "LETTER:COUNT", joined with "\n" — including letters with a count of 0. Write that report to out_path, and also return it.
Given in_path containing:
Ada,95
Alan,82
Grace,71
Kay,55
Lee,40
generate_grade_report(in_path, out_path) returns (and writes to out_path):
A:1
B:1
C:1
D:0
F:2
This problem chains together four separate skills from earlier in the course, in sequence. Breaking it into those stages is the key to not getting overwhelmed:
def generate_grade_report(in_path, out_path):
with open(in_path) as f:
lines = f.read().splitlines()
counts = {"A": 0, "B": 0, "C": 0, "D": 0, "F": 0}
for line in lines:
name, score_text = line.split(",")
score = int(score_text)
if score < 0 or score > 100:
raise ValueError(f"invalid score for {name}: {score}")
letter = grade_letter(score)
counts[letter] += 1
report = "\n".join(f"{letter}:{counts[letter]}" for letter in ["A", "B", "C", "D", "F"])
with open(out_path, "w") as f:
f.write(report)
return report
Stage 1 — read (File I/O): f.read().splitlines() gets one string per line, the same technique as Count CSV Rows.
Stage 2 — validate (Exceptions): for each line, parse the score and check its range before doing anything else with it — raising immediately with a message that names the offending student, the same guard-clause pattern as Validate Age. Note the validation happens inside the loop, per line — a single bad score should stop the whole report immediately (an exception propagating out of the function), not just get skipped.
Stage 3 — classify and count (Conditionals + Dictionaries): grade_letter reuses the exact if/elif chain from the Grade Letter problem. counts[letter] += 1 is the accumulator pattern from Word Frequency, except every possible key already exists in counts from the start (initialized to 0), so there's no need for .get() here — every letter returned by grade_letter is guaranteed to already be a key.
Stage 4 — format and write (File I/O again): the report needs a fixed letter order (A, B, C, D, F), not whatever order a dict happens to iterate in — that's why the join loops over a literal list of letters and looks each one up in counts, rather than iterating counts.items() directly. The same report string is both written to out_path and returned, satisfying both what the file should contain and what the function itself hands back to its caller.
def grade_letter(score):
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
else:
return "F"
def generate_grade_report(in_path, out_path):
with open(in_path) as f:
lines = f.read().splitlines()
counts = {"A": 0, "B": 0, "C": 0, "D": 0, "F": 0}
for line in lines:
name, score_text = line.split(",")
score = int(score_text)
if score < 0 or score > 100:
raise ValueError(f"invalid score for {name}: {score}")
letter = grade_letter(score)
counts[letter] += 1
report = "\n".join(f"{letter}:{counts[letter]}" for letter in ["A", "B", "C", "D", "F"])
with open(out_path, "w") as f:
f.write(report)
return report
Sign in to join the discussion.
No comments yet. Be the first to ask a question.
Press Run to execute your code, or Submit to test and complete this problem.