如何解析格式类似于成绩册的文本文件?
How to parse a text file that is formatted like a gradebook?
我正在尝试读取数据格式如下的文本文件:
Name|Test1|Test2|Test3|Test4|Test5|Test6|Test7|Test8|Test9|Test10
John Smith|82|89|90|78|89|96|75|88|90|96
Jane Doe|90|92|93|90|89|84|97|91|87|91
Joseph Cruz|68|74|78|81|79|86|80|81|82|87
我的目标是能够得到每个学生的平均考试成绩,以及每门考试的平均分(列)和总分的平均分。我无法将第一列(学生的姓名)与他们的考试成绩“分开”。有没有办法忽略或跳过第一列?另外,存储这些考试分数的最佳方式是什么,以便我能够进行我提到的那些计算?
我已经使用以下方法成功读取了文件的内容:
in.useDelimiter("\|");
for(int i = 0; in.hasNextLine(); i++){
System.out.println(in.next());}
解决方案
您可以通过在进入循环之前完全消耗第一行来实现您想要的,只需调用
in.nextLine();
之前第一行被消耗。
分裂
但是,我会采用不同的方法,逐行解析,然后在 |
上拆分,这样可以更轻松地处理每行给定的数据。
in.nextLine();
while (in.hasNextLine()) {
String line = in.nextLine();
String[] data = line.split("\|");
String name = data[0];
int[] testResults = new int[data.length - 1];
for (int i = 0; i < testResults.length; i++) {
testResults[i] = Integer.parseInt(data[i + 1]);
}
...
}
正确的 OOP
理想情况下,您可以向其中添加一些 OOP,创建一个 class Student
字段,例如
public class Student {
private final String name;
private final int[] testResults;
// constructor, getter, ...
}
然后给它一个 parseLine
方法,例如:
public static Student parseLine(String line) {
String[] data = line.split("\|");
String name = data[0];
int[] testResults = new int[data.length - 1];
for (int i = 0; i < testResults.length; i++) {
testResults[i] = Integer.parseInt(data[i + 1]);
}
return new Student(name, testResults);
}
然后你的解析大大简化为:
List<Student> students = new ArrayList<>();
in.nextLine();
while (in.hasNextLine()) {
students.add(Student.parseLine(in.nextLine());
}
流和 NIO
或者,如果您喜欢流,只需使用 NIO 读取文件即可:
List<Student> students = Files.lines(Path.of("myFile.txt"))
.skip(1)
.map(Student::parseLine)
.collect(Collectors.toList());
非常清晰、紧凑且可读。
平均分
My goal is to be able to get each student's average test score, as well as the average score per test (column) and the average score overall.
如图所示,使用适当的 OOP 结构,这相当简单。首先,一个学生的平均成绩,只需添加一个方法即可 Student
class:
public double getAverageScore() {
double total = 0.0;
for (int testResult : testResults) {
total += testResult;
}
return total / testResults.length;
}
替代流解决方案:
return IntStream.of(testResults).average().orElseThrow();
接下来,每列的平均分数:
public static double averageTestScore(List<Student> students, int testId) {
double total = 0.0;
for (Student student : students) {
total += student.getTestScores()[testId];
}
return total / students.size();
}
和流解决方案:
return students.stream()
.mapToInt(student -> student.getTestScores[testId])
.average().orElseThrow();
最后是总体平均分,可以通过取每个学生平均分的平均值来计算:
public static double averageTestScore(List<Student> students) {
double total = 0.0;
for (Student student : students) {
total += student.getAverageScore();
}
return total / students.size();
}
和流变体:
return students.stream()
.mapToDouble(Student::getAverageScore)
.average().orElseThrow();
我的想法是将您读取的数据存储在 Map
中。每个学生的名字是“键”,分数存储在 List<Integer>
中,您将其作为值放入映射中。
像这样:
Map<String, List<Integer>> scores = new HashMap<>();
List<Integer> studentScores = new ArrayList<>();
// then you read the scores one by one and add them
studentScores.add(82);
studentScores.add(89);
....
// when you are finished with the student you add him to the map
scores.put("John Smith", studentScores);
// in the end, when you need the values (for your calculation for example) you can get them like this:
scores.get("John Smith").get(0) // which will be the 1st value from John's list => 82
现在开始实际阅读:我认为您不需要定界符,只需阅读整行,然后 split
:
scanner.nextLine(); // I almost forgot: this reads and forgets the very first line of your file
while(scanner.hasNextLine()){
String line = scanner.nextLine(); // this is a whole line like "John Smith|82|89|....."
// now you need to split it
String[] columns = line.split("|"); // straight forward way to get an array that looks like this: ["John Smith", "82", "89", ...]
String studentName = columns[0]; // first we get the name
List<Integer> studentScores = new ArrayList<>();
for(int i=1;i<columns; i++){ // now we get the scores
studentScores.add(Integer.valueOf(columns[i])); // will read the score at index i, cast it to an Integer and add it to the score list
}
// finally you put everything in your map
scores.put(studentName, studentScores);
}
也许尝试使用 in.nextLine()
:
//to skip first line with headers
in.nextLine();
while (in.hasNextLine()) {
String studentLine = in.nextLine();
int firstColumnEnd = studentLine.indexOf("|");
String name = studentLine.substring(0, firstColumnEnd - 1);
String[] tests = studentLine.substring(firstColumnEnd + 1).split("\|");
}
我正在尝试读取数据格式如下的文本文件:
Name|Test1|Test2|Test3|Test4|Test5|Test6|Test7|Test8|Test9|Test10
John Smith|82|89|90|78|89|96|75|88|90|96
Jane Doe|90|92|93|90|89|84|97|91|87|91
Joseph Cruz|68|74|78|81|79|86|80|81|82|87
我的目标是能够得到每个学生的平均考试成绩,以及每门考试的平均分(列)和总分的平均分。我无法将第一列(学生的姓名)与他们的考试成绩“分开”。有没有办法忽略或跳过第一列?另外,存储这些考试分数的最佳方式是什么,以便我能够进行我提到的那些计算?
我已经使用以下方法成功读取了文件的内容:
in.useDelimiter("\|");
for(int i = 0; in.hasNextLine(); i++){
System.out.println(in.next());}
解决方案
您可以通过在进入循环之前完全消耗第一行来实现您想要的,只需调用
in.nextLine();
之前第一行被消耗。
分裂
但是,我会采用不同的方法,逐行解析,然后在 |
上拆分,这样可以更轻松地处理每行给定的数据。
in.nextLine();
while (in.hasNextLine()) {
String line = in.nextLine();
String[] data = line.split("\|");
String name = data[0];
int[] testResults = new int[data.length - 1];
for (int i = 0; i < testResults.length; i++) {
testResults[i] = Integer.parseInt(data[i + 1]);
}
...
}
正确的 OOP
理想情况下,您可以向其中添加一些 OOP,创建一个 class Student
字段,例如
public class Student {
private final String name;
private final int[] testResults;
// constructor, getter, ...
}
然后给它一个 parseLine
方法,例如:
public static Student parseLine(String line) {
String[] data = line.split("\|");
String name = data[0];
int[] testResults = new int[data.length - 1];
for (int i = 0; i < testResults.length; i++) {
testResults[i] = Integer.parseInt(data[i + 1]);
}
return new Student(name, testResults);
}
然后你的解析大大简化为:
List<Student> students = new ArrayList<>();
in.nextLine();
while (in.hasNextLine()) {
students.add(Student.parseLine(in.nextLine());
}
流和 NIO
或者,如果您喜欢流,只需使用 NIO 读取文件即可:
List<Student> students = Files.lines(Path.of("myFile.txt"))
.skip(1)
.map(Student::parseLine)
.collect(Collectors.toList());
非常清晰、紧凑且可读。
平均分
My goal is to be able to get each student's average test score, as well as the average score per test (column) and the average score overall.
如图所示,使用适当的 OOP 结构,这相当简单。首先,一个学生的平均成绩,只需添加一个方法即可 Student
class:
public double getAverageScore() {
double total = 0.0;
for (int testResult : testResults) {
total += testResult;
}
return total / testResults.length;
}
替代流解决方案:
return IntStream.of(testResults).average().orElseThrow();
接下来,每列的平均分数:
public static double averageTestScore(List<Student> students, int testId) {
double total = 0.0;
for (Student student : students) {
total += student.getTestScores()[testId];
}
return total / students.size();
}
和流解决方案:
return students.stream()
.mapToInt(student -> student.getTestScores[testId])
.average().orElseThrow();
最后是总体平均分,可以通过取每个学生平均分的平均值来计算:
public static double averageTestScore(List<Student> students) {
double total = 0.0;
for (Student student : students) {
total += student.getAverageScore();
}
return total / students.size();
}
和流变体:
return students.stream()
.mapToDouble(Student::getAverageScore)
.average().orElseThrow();
我的想法是将您读取的数据存储在 Map
中。每个学生的名字是“键”,分数存储在 List<Integer>
中,您将其作为值放入映射中。
像这样:
Map<String, List<Integer>> scores = new HashMap<>();
List<Integer> studentScores = new ArrayList<>();
// then you read the scores one by one and add them
studentScores.add(82);
studentScores.add(89);
....
// when you are finished with the student you add him to the map
scores.put("John Smith", studentScores);
// in the end, when you need the values (for your calculation for example) you can get them like this:
scores.get("John Smith").get(0) // which will be the 1st value from John's list => 82
现在开始实际阅读:我认为您不需要定界符,只需阅读整行,然后 split
:
scanner.nextLine(); // I almost forgot: this reads and forgets the very first line of your file
while(scanner.hasNextLine()){
String line = scanner.nextLine(); // this is a whole line like "John Smith|82|89|....."
// now you need to split it
String[] columns = line.split("|"); // straight forward way to get an array that looks like this: ["John Smith", "82", "89", ...]
String studentName = columns[0]; // first we get the name
List<Integer> studentScores = new ArrayList<>();
for(int i=1;i<columns; i++){ // now we get the scores
studentScores.add(Integer.valueOf(columns[i])); // will read the score at index i, cast it to an Integer and add it to the score list
}
// finally you put everything in your map
scores.put(studentName, studentScores);
}
也许尝试使用 in.nextLine()
:
//to skip first line with headers
in.nextLine();
while (in.hasNextLine()) {
String studentLine = in.nextLine();
int firstColumnEnd = studentLine.indexOf("|");
String name = studentLine.substring(0, firstColumnEnd - 1);
String[] tests = studentLine.substring(firstColumnEnd + 1).split("\|");
}