Section 4.1 Grading Reporter: A small project
Subsection 4.1.1 The problem
A core philosophy of this book is to introduce many of the ideas via programming projects, In this chapter we will start one such project, a simple grading reporter.
The premise of the reporter is simple: It reads data entries from a file, which correspond to courses with letter grades in them, using the standard letter grade scales prevalent in most academic settings. It then computes an average GPA and reports some more info. Simple, right? As an example, this reporter would read the following text:
CS 234 A MAT 111 B
Then it would report back something like this:
Courses: 2 GPA: 3.50
By itself this program isn’t doing much of course. But over time we will expand on it, to make it do more. And in any case it is a good starting point from which we can discuss many ideas.
Here is a start code for this:
https://github.com/sdp-resources/grades.git
Before reading the solution, you should try to code this project yourself, starting from that code, by filling in the class to see what methods you can use to read each row’s information.
processGrades
method. The file contains tests you can run to check your work. You should read about Java’s Scanner1
docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Scanner.html
You should spend at least an hour working on this, but preferably not much more. When you are done, read on to see a basic solution.
Subsection 4.1.2 First solution
I should preface this section by saying that this will be far from an example at a good solution. But it is a straightforward procedural solution, and many students start from something like this. We will learn later how to turn this into a more object-oriented solution.
I will also work on this incrementally. Each code block below will show a version of the body of the
processGrades
function. I will do my best to make it clear whether it is supposed to be the entire function or a part of it.Let’s start by thinking through the situation a bit. We need to arrange for a few things. First of all, I need to return as information how many courses we are dealing with and also what their GPA is. So it sounds like I might need to keep track of that information. So I am thinking I will need two local variables, one for number of courses, I will call it simply
n
and one for the "total gpa", I will just call it t
. Those of you with some programming experience will cringe at my choice of variable names, and rightly so. In later chapters we will discuss good naming principles and refactoring, and we will return to this example there. But for now, this gives me a great start to my processGrades
method:int n = 0; // number of courses
double t = 0.00; // total gpa
Next up, I imagine I will need some way to loop over each row in the file. Since I don’t know how many rows there are in advance, this sounds more like a
while
loop than a for
loop. So let’s start it out, something like this:int n = 0; // number of courses
double t = 0.00; // total gpa
while (.. there are more rows ..) {
.. read next row and update the values ..
}
Then I better also have a way to return a string with the correct info at the end. But let’s work on that loop first. I’ll need to know if I reached the end of my file. Lucky for me, the
Scanner
class has a nice method called hasNext()
. It will simply tell me if there is anything more to read that is not just whitespace characters. Ideally I would write code that also checks for malformed lines, but for now let’s assume each row that is there will have valid stuff. So my while loop’s check is now each:int n = 0; // number of courses
double t = 0.00; // total gpa
while (scanner.hasNext()) {
.. read next row and update the values ..
}
Time to work on the big part inside the
while
loop, where the "real action" happens! I see the Scanner
class has a next()
function that will simply read the next "token", i.e. the next sequence of characters before a whitespace character. Looking at my row structure it looks like I have to call this thing three times in order to get the letter grade:int n = 0; // number of courses
double t = 0.00; // total gpa
while (scanner.hasNext()) {
scanner.next(); // The course prefix
scanner.next(); // The course number
String lg = scanner.next(); // The letter grade
}
Hm, so far so good. Now I need to decide what to do with that letter grade. One of the things you’ll learn from the provided tests, is that the letter grade of
W
(withdrawn from course) is not supposed to count at all. All other grades will count towards the GPA, so I should increment the n
variable in that case. I can add an easy if
check for that:String lg = scanner.next(); // The letter grade
if (!lg.equals("W")) n += 1;
Notice the logical negation operator,
!
, and our usage of equals
. The equals
method checks when two items are equal to each other. For objects that would traditionally mean that they have the same place in memory, unless someone has implemented a more appropriate method, and they often have. In this case two strings are equals
if they are the same sequence of characters.I also chose to put the whole thing in one line, and not use curly braces. This is simply because the operation I am doing is very short and simple.
Next I need to account for the gpa increase. Sounds like I need to do a big switch statement for that, to account for each possible letter grade. We’ll consider other options later. But for now this would work:
switch (lg) {
case "A":
t += 4.00;
break;
case "A-":
t += 3.67;
break;
...
default:
}
I am omitting here the other letter grades,
B+
, B
, B-
etc, you will need one case for each, with the points going down by 0.33
each time.Those of you with some more programming experience would rightly be appalled by this code, and at the very least expect it all to be in a separate method. We will work on that at a later section. For now we are just getting a straightforward solution down.
One last thing remains, to manage to print out the result, in an expected form. This can be achieved nicely by using the
String.format
method. But first we need to calculate the gpa by dividing the total by the number of courses. We need to do a special case if there were no courses, so as to avoid dividing by 0. The ternary operator t ? a : b
comes in handy for that: } // end of the while loop
double gpa = n == 0 ? 0.00 : t / n;
return String.format("Courses: %d%nGPA: %.2f%n", n, gpa);
}
This should now pass all our tests! It’s a start. We’ll clean it up later.
Subsection 4.1.3 Practice
Try these out in a copy of the code. We will be using the above solution as a starting point for future examples, so you don’t want to lose track of it.
- Add code that prints a header of some sort, then a separator line, before the results.
- Allow
P
andF
as pass/fail options that should be ignored for the purposes of gpa computations just like "W". - Extend this work to remember the course prefix for each course, and to report total number of courses for each prefix (e.g. CS separately etc). A map will be handy for that.
- Add code that prints each row in some format (specific spacing, grades aligned in a column), then prints a separator line before the summary.