patternjavaMinor
A command line Java program for computing grade point average
Viewed 0 times
linepointgradeprogramaveragejavaforcommandcomputing
Problem
(See the next iteration.)
I have this command line program for computing GPA (grade point average). Given credit sequence \$\langle c_1, c_2, \dots, c_n \rangle\$ and grade sequence \$\langle g_1, g_2, \dots, g_n \rangle\$ (I assume European grades, i.e., \$g_i \in \{ 1, 2, 3, 4, 5 \}\$ for all \$i \in \{1, 2, \dots, n \} \$), grade point average is
$$\frac{\sum_{i = 1}^n c_i g_i}{\sum_{i = 1}^n c_i}.$$
Being invoked without arguments, the program will expect input from keyboard. With one argument (which must be a CSV file with two columns), the program computes the GPA, prints it, and exits.
I tried to make the program robust: it reports all errors without crashing (except whenever the input file does not exist).
App.java
```
package net.coderodde.gpa;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* This class implements a command line application for computing GPA (grade
* point average).
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Mar 20, 2016)
*/
public class App {
private final List creditList = new ArrayList<>();
private final List gradeList = new ArrayList<>();
private final Scanner scanner;
public App(String fileName) {
if (fileName == null) {
scanner = new Scanner(System.in);
} else {
try {
scanner = new Scanner(new File(fileName));
} catch (FileNotFoundException ex) {
throw new RuntimeException(
"File \"" + fileName + "\" does not exist.", ex);
}
}
}
public static void main(String[] args) {
if (args.length > 1) {
printHelp();
return;
}
try {
App app = new App(args.length == 0 ? null : args[0]);
app.populateLists();
System.out.println(app.computeGradePointAverage());
} catch (Exception ex) {
I have this command line program for computing GPA (grade point average). Given credit sequence \$\langle c_1, c_2, \dots, c_n \rangle\$ and grade sequence \$\langle g_1, g_2, \dots, g_n \rangle\$ (I assume European grades, i.e., \$g_i \in \{ 1, 2, 3, 4, 5 \}\$ for all \$i \in \{1, 2, \dots, n \} \$), grade point average is
$$\frac{\sum_{i = 1}^n c_i g_i}{\sum_{i = 1}^n c_i}.$$
Being invoked without arguments, the program will expect input from keyboard. With one argument (which must be a CSV file with two columns), the program computes the GPA, prints it, and exits.
I tried to make the program robust: it reports all errors without crashing (except whenever the input file does not exist).
App.java
```
package net.coderodde.gpa;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* This class implements a command line application for computing GPA (grade
* point average).
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Mar 20, 2016)
*/
public class App {
private final List creditList = new ArrayList<>();
private final List gradeList = new ArrayList<>();
private final Scanner scanner;
public App(String fileName) {
if (fileName == null) {
scanner = new Scanner(System.in);
} else {
try {
scanner = new Scanner(new File(fileName));
} catch (FileNotFoundException ex) {
throw new RuntimeException(
"File \"" + fileName + "\" does not exist.", ex);
}
}
}
public static void main(String[] args) {
if (args.length > 1) {
printHelp();
return;
}
try {
App app = new App(args.length == 0 ? null : args[0]);
app.populateLists();
System.out.println(app.computeGradePointAverage());
} catch (Exception ex) {
Solution
The D in SOLID stands for the Dependency Inversion Principle, which states that we should depend upon abstractions, not concretions. We can therefore improve the
And we would push determining which
In the original code, we are adding related data to two separate
With our new
We are starting to see a more coherent model emerge out of the problem. Looking now, we can see there are two distinct parts to the code: reading in the data, and doing calculations on the data. We can separate these two parts to make our code more modular.
We need a better name for the data we are working with, though. Since we are dealing with grade data at a university level, we can look to that domain for our name: Transcript. A transcript is a history of grade data for a student over their entire studies. This assumes that the grade data is for a single student, an assumption I am willing to make at this point. For a similar idea at pre-university levels, Report Card is an equivalent nomenclature.
So we have a part of the code that reads in a transcript, and part of the code that represents that transcript. Our
```
public class TranscriptReader {
private Scanner in;
public TranscriptReader(InputStream in) {
scanner = new Scanner(in);
}
public List readTranscript() {
List transcript = new ArrayList();
int lineNumber = 1;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.equals("end")) {
return;
}
if (line.isEmpty()) {
lineNumber++;
continue;
}
if (!line.contains(",")) {
System.err.println(
"Line " + lineNumber + " does not have a comma.");
lineNumber++;
continue;
}
String[] parts = line.split(",");
if (parts.length != 2) {
System.err.println(
"Line " + lineNumber + " contains invalid number of " +
"tokens (" + parts.length + "). Should have exactly " +
"two: number of credits and the grade.");
continue;
}
String creditsString = parts[0].trim();
String gradeString = parts[1].trim();
int credits;
int grade;
try {
credits = Integer.parseInt(creditsString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid credit token on line " + lineNumber +
": " + creditsString);
++lineNumber;
continue;
}
try {
grade = Integer.parseInt(gradeString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid grade token on line " + lineNumber +
": "+ gradeString);
++lineNumber;
continue;
}
transcr
App class by depending on receiving an abstraction for reading in the data:public App(InputStream in) {
scanner = new Scanner(in);
}And we would push determining which
InputStream we receive up to the main method:public static void main(String[] args) throws FileNotFoundException {
if (args.length > 1) {
printHelp();
return;
}
InputStream in = args.length == 0 ? System.in : new FileInputStream(args[0]);
App app = new App(in);
app.populateLists();
System.out.println(app.computeGradePointAverage());
}In the original code, we are adding related data to two separate
Lists. This means that we have to keep two pieces of data in sync across separate data structures. This may not seem like much of a burden here, but as applications grow more complicated and data structures are passed around, we can see data become harder to keep in sync. This also means that we are not capturing some semantic information about our data. If we pass creditList where we mean to pass gradeList, there is no way the compiler can tell us our mistake. To solve both of these problems, we can create a small class, which I will call Course:public class Course {
private final int credits;
private final int grade;
public Course(int credits, int grade) {
this.credits = credits;
this.grade = grade;
}
public int getCredits() { return credits; }
public int getGrade() { return grade; }
}With our new
Course class, we can replace the two Lists with a single List. But wait, something interesting can now happen here. We've pulled the data we need for calculating our credit-weighted grade into here, so why don't we also pull the calculation as well?public int getCreditWeightedGrade() {
return getCredits() * getGrade();
}We are starting to see a more coherent model emerge out of the problem. Looking now, we can see there are two distinct parts to the code: reading in the data, and doing calculations on the data. We can separate these two parts to make our code more modular.
We need a better name for the data we are working with, though. Since we are dealing with grade data at a university level, we can look to that domain for our name: Transcript. A transcript is a history of grade data for a student over their entire studies. This assumes that the grade data is for a single student, an assumption I am willing to make at this point. For a similar idea at pre-university levels, Report Card is an equivalent nomenclature.
So we have a part of the code that reads in a transcript, and part of the code that represents that transcript. Our
List is the representative of the transcript itself. We will rename the App class to TranscriptReader to encapsulate that concept:```
public class TranscriptReader {
private Scanner in;
public TranscriptReader(InputStream in) {
scanner = new Scanner(in);
}
public List readTranscript() {
List transcript = new ArrayList();
int lineNumber = 1;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.equals("end")) {
return;
}
if (line.isEmpty()) {
lineNumber++;
continue;
}
if (!line.contains(",")) {
System.err.println(
"Line " + lineNumber + " does not have a comma.");
lineNumber++;
continue;
}
String[] parts = line.split(",");
if (parts.length != 2) {
System.err.println(
"Line " + lineNumber + " contains invalid number of " +
"tokens (" + parts.length + "). Should have exactly " +
"two: number of credits and the grade.");
continue;
}
String creditsString = parts[0].trim();
String gradeString = parts[1].trim();
int credits;
int grade;
try {
credits = Integer.parseInt(creditsString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid credit token on line " + lineNumber +
": " + creditsString);
++lineNumber;
continue;
}
try {
grade = Integer.parseInt(gradeString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid grade token on line " + lineNumber +
": "+ gradeString);
++lineNumber;
continue;
}
transcr
Code Snippets
public App(InputStream in) {
scanner = new Scanner(in);
}public static void main(String[] args) throws FileNotFoundException {
if (args.length > 1) {
printHelp();
return;
}
InputStream in = args.length == 0 ? System.in : new FileInputStream(args[0]);
App app = new App(in);
app.populateLists();
System.out.println(app.computeGradePointAverage());
}public class Course {
private final int credits;
private final int grade;
public Course(int credits, int grade) {
this.credits = credits;
this.grade = grade;
}
public int getCredits() { return credits; }
public int getGrade() { return grade; }
}public int getCreditWeightedGrade() {
return getCredits() * getGrade();
}public class TranscriptReader {
private Scanner in;
public TranscriptReader(InputStream in) {
scanner = new Scanner(in);
}
public List<Course> readTranscript() {
List<Course> transcript = new ArrayList<Course>();
int lineNumber = 1;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.equals("end")) {
return;
}
if (line.isEmpty()) {
lineNumber++;
continue;
}
if (!line.contains(",")) {
System.err.println(
"Line " + lineNumber + " does not have a comma.");
lineNumber++;
continue;
}
String[] parts = line.split(",");
if (parts.length != 2) {
System.err.println(
"Line " + lineNumber + " contains invalid number of " +
"tokens (" + parts.length + "). Should have exactly " +
"two: number of credits and the grade.");
continue;
}
String creditsString = parts[0].trim();
String gradeString = parts[1].trim();
int credits;
int grade;
try {
credits = Integer.parseInt(creditsString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid credit token on line " + lineNumber +
": " + creditsString);
++lineNumber;
continue;
}
try {
grade = Integer.parseInt(gradeString);
} catch (NumberFormatException ex) {
System.err.println(
"Invalid grade token on line " + lineNumber +
": "+ gradeString);
++lineNumber;
continue;
}
transcript.add(new Course(credits, grade));
lineNumber++;
}
return transcript;
}
}Context
StackExchange Code Review Q#123367, answer score: 7
Revisions (0)
No revisions yet.