Beginner project · Medium
Gradebook report
You start from a small gradebook held in your program: students, each with a list of scores. Your program prints a table with one row per student, showing the scores, an average to one decimal place and a letter grade, with the columns lined up whatever the names are. Then it prints a summary: the class average, the highest and lowest averages, and how many students fall in each grade band.
- Languages
- Python
- Size
- Medium: a few sessions
- Where to build it
- Runs as-is in the Python playground; no input box is needed because the gradebook lives in the code. Open the Python playground →
What you will practise
- lists of dictionaries as a small data model
- string formatting with widths and alignment
- rounding and deciding where rounding happens
- writing functions that take data and return data
- handling the empty case without a crash
- sorting by a computed value
Requirements
The project is done when every one of these is true.
- The gradebook is one data structure at the top of the file, and adding a student means editing only that structure.
- The report prints a header row and one row per student, with the name column wide enough for the longest name plus padding, so every column lines up exactly.
- Each student's average is printed to exactly one decimal place.
- Each student gets a letter grade from a band table you define in the code: 90 and above A, 80 to 89.9 B, 70 to 79.9 C, 60 to 69.9 D, below 60 F.
- The class average is printed to one decimal place and is computed from all the individual scores, not from the already-rounded per-student averages.
- A student with an empty score list shows a dash in the average and grade columns instead of raising an error.
- The summary reports the highest and lowest student averages with the student names, and a count for each letter grade.
- The letter-grade decision lives in exactly one function, called once per student, rather than being repeated inline.
Milestones
A sensible order to build it in, so something works at every step.
Define the gradebook and print it raw
Write three or four students with score lists and print the structure as-is, so you know the shape you are working with before you format anything.
Write the average function
Write one function that takes a list of scores and returns the average, deciding right now what it returns for an empty list, then call it for each student.
Write the letter-grade function
Write one function that maps a numeric average to a letter using your band table, and check the boundary values by hand before moving on.
Line up the columns
Find the longest name, use it to set the name column width, and format every row with the same widths so the header and the rows align.
Add the summary block
Compute the class average from every score, find the highest and lowest student averages with their names, and count the students in each band.
Handle the awkward cases
Add a student with no scores and a class with no students at all, and make both print something sensible rather than raising an error.
Hints
Open one only when you are stuck. Each gives a little more away.
Show hint 1Hint 1
Format specifiers can take a width, and that width can be a variable rather than a literal, which is how one format string serves names of any length.
Show hint 2Hint 2
Averaging averages is not the same as averaging all the values when students have different numbers of scores. Decide which one you mean and say so in a comment.
Show hint 3Hint 3
Rounding for display and rounding for arithmetic are different jobs. Keep the full-precision average in your data and round only where you print it.
Show hint 4Hint 4
Dividing by the length of a list is the whole bug for the empty case. Check the length before you divide and return a value that means no data.
Show hint 5Hint 5
Grade bands are easier to get right as a list of pairs you loop over, from the highest band downwards, than as a chain of comparisons you have to keep in your head.
Show hint 6Hint 6
Print the header row through the same formatting code as the data rows. If they go through different code they will drift apart the first time you change a width.
How to test it
Run these checks yourself, or turn them into automated tests once you know how.
- With Ana on 88, 92, 79, her average prints as 86.3 and her grade as B, because 259 divided by 3 is 86.33.
- With Ben on 70, 65, 81, his average prints as 72.0 and his grade as C, because 216 divided by 3 is exactly 72.
- With Cleo on 95, 100, 91, her average prints as 95.3 and her grade as A, because 286 divided by 3 is 95.33.
- With exactly those three students, the class average prints as 84.6, because the nine scores add up to 761 and 761 divided by 9 is 84.56.
- The summary should name Cleo as highest and Ben as lowest for that same class, with one A, one B and one C and no D or F.
- Give a student the single score 90 and confirm the grade is A, then change it to 89.9 and confirm it becomes B, which checks your band boundary.
- Add a student called Bartholomew with any scores and confirm every column still lines up, proving the width is computed rather than hard-coded.
- Add a student with an empty score list and confirm you get a dash in the average and grade columns and no error, and that the class average ignores them.
Stretch goals
- Add weighted categories, such as homework worth 40 per cent and exams 60, and show the weighted average.
- Sort the rows by average, highest first, and add a rank column that shares a rank for ties.
- Add a column showing each student's difference from the class average, with a sign.
- Print a simple text histogram of the grade bands using repeated characters.
- Drop each student's lowest score before averaging, and show both averages side by side.