一、前言
本次blog是针对发布题目集1~3的的最后一题的总结。三次大作业都是模拟一个小型的在线测试系统,先由大作业1完成基本功能,后面进行迭代,不断增加功能,丰富功能。下面具体分析这三次大作业的关键点和区别。
1、答题程序-1
1)输入:题目数量:首先输入题目数量。题目信息:按指定格式输入题目和标准答案。答题信息:记录学生的解答,格式约定答案顺序与题目顺序相对应。
2)实现了最基础的答题判题功能,主要输入题目信息和答题信息,并根据题目数量和答案进行判分。题目数量与答题信息的数量必须一致。
3) 关键点:利用正则表达式和字符串的拆分,解析出题目和答题信息。然后对每个题目判断学生的答案是否与标准答案一致,并输出 true 或 false,最后根据判题结果输出答题信息和判分信息。
2、答题程序-2
1)输入:题目信息:输入题目编号、内容和标准答案。试卷信息:增加试卷的编号和题目编号、分值。答卷信息:记录学生的答卷信息,并判分。
2)在程序-1的基础上,增加了试卷的概念。输入的顺序可以打乱,题目信息、试卷信息和答题信息可以混合输入。支持题目编号缺失的情况,程序仍正常处理。
3) 关键点:利用正则表达式和字符串的拆分,解析出题目、答题和试卷的信息。首先检查试卷总分是否为100分,不等于100分则输出警示。对每道题目进行判断,并根据答案输出 true 或 false。支持输出答题信息缺失提示 answer is null。根据每道题的分值计算学生的总分。
3、答题程序-3
1)输入:题目信息:管理题目编号、内容、标准答案。试卷信息:包含题目编号和分值,引用题目必须有效。学生信息:记录学号和姓名,答卷中学号需对应。删除题目信息:支持删除某道题目,并在答卷输出中标明题目失效。答卷信息:记录学生答题和学号。
2)在程序-2的基础上增加了学生信息管理。支持删除题目的功能,处理题目删除后试卷中对应题目失效的情况。增加了多种输入格式的错误提示,增强了系统的健康性。
3) 关键点:利用正则表达式和字符串的拆分,解析出题目、答题、试卷、学生和删除的信息。删除的题目会输出 the question X invalid 并计0分。引用不存在的题目提示 non-existent question。检查试卷总分不等于100分时输出警示。学号不在学生列表中提示学号引用错误,但答题照常输出。如果答卷信息中试卷的编号找不到,则输出”the test paper number does not exist”。输入信息只要不符合格式要求,均输出”wrong format:”+信息内容。如果答案输出时,一道题目同时出现答案不存在、引用错误题号、题目被删除,只提示一种信息,答案不存在的优先级最高。
二、设计分析
设计合理简洁的结构图可以使代码编写变得简便,因此这部分对三次答题程序的结构、变量、方法进行分析,分别画出他们的类图的顺序图。
1、答题程序-1
1)类图
2)顺序图
3)主函数
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入题目数量int questionCount = Integer.parseInt(scanner.nextLine().trim());TestPaper testPaper = new TestPaper();// 输入题目内容for (int i = 0; i < questionCount; i++) {String line = scanner.nextLine().trim();String[] parts = line.split(" #");int number = Integer.parseInt(parts[0].replace("#N:", "").trim());String content = parts[1].replace("#Q:", "").trim(); // 去掉"Q:"String standardAnswer = parts[2].replace("#A:", "").trim();testPaper.addQuestion(new Question(number, content, standardAnswer));}// 输入答题信息AnswerSheet answerSheet = new AnswerSheet(testPaper);String answerLine;while (!(answerLine = scanner.nextLine().trim()).equals("end")) {String[] answerParts = answerLine.replace("#A:", "").trim().split(" ");for (String answer : answerParts) {answerSheet.addAnswer(answer);}}// 判题answerSheet.evaluate();// 输出结果answerSheet.printResults();scanner.close();
}
}
先输入题目数量,再输入题目内容,再输入答题信息,输入完成后,调用判题和输出结果函数。
2、答题程序-2
1)类图
2)顺序图
3)主函数
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Agent agent = new Agent(); // 创建题目和试卷的管理类
while (scanner.hasNextLine()) {String input = scanner.nextLine().trim();if (input.equals("end")) break;if (input.startsWith("#N:")) {agent.Questioninput(input); // 解析题目输入} else if (input.startsWith("#T:")) {agent.Testinput(input); // 解析试卷输入} else if (input.startsWith("#S:")) {agent.Answerinput(input); // 解析答卷输入}}// 对所有答卷进行判题并输出结果agent.printresult();}
}
由于输入较为复杂,相较于答题程序1,将输入部分放在Agent类中,主函数值调用题目、试卷、答卷解析函数,最后调用判题和输出结果函数。
3、答题程序-3
1)类图
2)顺序图
3)主函数
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Agent agent = new Agent();
while (scanner.hasNextLine()) {String input = scanner.nextLine().trim();if (input.equals("end")) {break;} else if (input.startsWith("#N:")) {agent.parseQuestionInput(input);} else if (input.startsWith("#T:")) {agent.parseTestInput(input);} else if (input.startsWith("#X:")) {agent.parseStudentInput(input);} else if (input.startsWith("#S:")) {agent.parseAnswerInput(input);} else if (input.startsWith("#D:")) {agent.parseDeleteInput(input);}}agent.printResult();
}
}
与答题程序2一样,输出部分放在Agent类中,主函数调用题目、答题、试卷、学生和删除解析函数,调用判题和输出结果函数。
三、踩坑心得
这部分对大作业中遇到的错误进行总结,在第一部分总结的关键点中,有很多编程时容易遇到很多错误。
1、答题程序-1
1)拆分字符串出现问题
情况描述:本题需要解析出题目#N和答题#A,总是不能解析到正确的题目和答题。输入格式非常严格,空格也会影响解析结果。
解决方法:由于为本行输入,所以需要多次用到分隔符split;去除空格的影响,再多次使用trim()。
如:解析题目内容函数如下,多次使用了分隔符和trim(),去除空格影响。
// 输入题目内容
for (int i = 0; i < questionCount; i++) {
String line = scanner.nextLine().trim();
String[] parts = line.split(" #");
int number = Integer.parseInt(parts[0].replace("#N:", "").trim());
String content = parts[1].replace("#Q:", "").trim(); // 去掉"Q:"
String standardAnswer = parts[2].replace("#A:", "").trim();
testPaper.addQuestion(new Question(number, content, standardAnswer));
}
2)题目和答题信息顺序不一致
情况描述:在实现评分功能时,我发现题号顺序和答题顺序不匹配会导致判分出错。例如,题库的题号为 1, 2, 3, 4,而答卷中题目顺序为 3, 2, 1, 4。
解决方法:判断是否正确是,不要按顺序处理,而是根据题号来进行对比。
public Question getQuestionByNumber(int number) {
for (Question question : questions) {
if (question.getNumber() == number) {
return question;
}
}
return null;
}
2、答题程序-2
1)试卷中缺少题目或题号不一致
情况描述:由于本题中,试卷题目缺失,试卷仍然有效,因此需要再答题程序1基础上进行改进。
解决方法:根据题目编号来查找题目,而不按照解析出的顺序,如果出现没有题目的答案,输出提示信息。
public void printresult() {
for (Answer answerSheet : answers) {
Test test = findTest(answerSheet.testNumber);
if (test != null) {
String result = test.grade(answerSheet, questions);
System.out.println(result);
}
}
}
2)各种输入情况出现时,输出顺序出错。
情况描述:乱序输入+分值不足100+两份答卷+答卷缺失部分答案时,当输入
N:3 #Q:3+2= #A:5
N:2 #Q:2+2= #A:4
T:1 3-7 2-6
S:1 #A:5 #A:22
N:1 #Q:1+1= #A:2
T:2 2-5 1-3 3-2
S:2 #A:5 #A:4
end
输出为
alert: full score of test paper1 is not 100 points
3+2=5true
2+2=22false
7 0~7
alert: full score of test paper2 is not 100 points
2+2=5false
1+1=4false
answer is null
0 0 0~0
解决方法:需要格外注重输出的顺序,先将两张试卷的是否满100分情况先输出。作业才能得到预期输出结果。
alert: full score of test paper1 is not 100 points
alert: full score of test paper2 is not 100 points
3+2=5true
2+2=22false
7 0~7
2+2=5false
1+1=4false
answer is null
0 0 0~0
3、答题程序-3
1)无效的试卷引用,需要输出”the test paper number does not exist”
情况描述:当输入
N:1 #Q:1+1= #A:2
T:1 1-5
X:20201103 Tom
S:2 20201103 #A:1-5 #A:2-4
end
结果输出值显示了alert: full score of test paper1 is not 100 points
解决方法:新增试卷号,进行答卷判断是,要根据试卷号进行检查,然后没有该试卷号,要输出提示信息。
public Test findTest(int testNumber) {
for (Test test : tests) {
if (test.testNumber == testNumber) {
return test;
}
}
return null;
}
2)无效的学生引用,学号不在学生列表中提示学号引用错误,但答题照常输出。
举例:输入:
N:1 #Q:1+1= #A:2
T:1 1-5
X:20201106 Tom
S:1 20201103 #A:1-5 #A:2-4
end
预期输出:
alert: full score of test paper1 is not 100 points
1+1=5false
20201103 not found
解决方法:用try...catch捕获,学号不存在时,答题照常输出并进行判题,最后输出提示信息学号不存在。
public void parseStudentInput(String input) {
try {
input = input.substring(3);
String[] studentParts = input.split("-");
for (String studentInfo : studentParts) {String[] parts = studentInfo.trim().split(" ");if (parts.length == 2) {String studentId = parts[0].trim();String studentName = parts[1].trim();students.put(studentId, studentName);} else {System.out.println("wrong format: " + input);return;}}} catch (Exception e) {System.out.println("wrong format: " + input);}
}
3)如果答案输出时,一道题目同时出现答案不存在、引用错误题号、题目被删除,只提示一种信息,答案不存在的优先级最高。
举例:含错误格式输入、有效删除、无效题目引用信息以及答案没有输入的情况。输入:
N:1 +1= #A:2
N:2 #Q:2+2= #A:4
T:1 1-5 2-8
X:20201103 Tom-20201104 Jack-20201105 Www
S:1 20201103 #A:1-5
D:N-2
end
预期输出:
wrong format:#N:1 +1= #A:2
alert: full score of test paper1 is not 100 points
non-existent question~0
answer is null
20201103 Tom: 0 0~0
解决方法:新增错误信息集合,并设置错误信息优先级。
4)删除信息等多种复杂情况的输入时,需要注意输出顺序,以及对所有输入信息均要检验,不符合输入的均要输出提示信息。
四、改进建议
三次大作业的答题程序都没有满分,因此程序还有很多问题,还存在很大的进步空间。
1、答题程序-1
需要解决当答题数量小于题目数量情况时,输出错误的问题。
2、答题程序-2
需要优化混合输入的解析顺序,正确捕获需要的信息。在处理各种判断情况时,应该清晰编写判断函数。输出函数。
3、答题程序-2
需要解决无效的学生引用,学号不在学生列表中提示学号引用错误,答题不能照常输出的问题和输出信息优先级。Agent类中方法功能较多,可以考虑增加Student类,将相关代码放入,让整体框架更为简洁。题目涉及删除学生信息,还可以考虑增删改查功能。
总而言之,在通过更多测试样例的前提下,不断清晰整体框架,改进解析输入方法和输出信息方法,之后再考虑向其他功能的改进。
五、总结
java语言程序设计是本学期才开设的课程,所以接触java语法时间较短,完成这三次答题程序的迭代,需要用到很多没有接触的java语句、语法。虽然遇到很多的问题、错误,但也让我学习到了很多东西。最让我印象深刻的便是三次答题程序的输入解析,面对大量的输入信息,正则表达式可以快速帮我们进行匹配,分隔符split可以快速帮我们进行分组,帮助我们提取到正确的输入信息。面对这种大量信息需要处理的程序时,编写时,一定要先将结构划分清楚,明确有哪些类,哪些功能,类与类之间的关系,作业能使编程更加简单。同时,通过这三次大作业,我意识到方法和变量合理、简单命名的重要性。一个简单、一眼知晓函数功能可以让我们梳理代码逻辑、检查代码错误时事半功倍,省事省力。同时,为了代码的健壮性,我们应该在代码中多设置一下输入输出提示,并注意他们的位置,达到既不凌乱又能给人提示的目的。通过这三个复杂的程序设计,我不仅熟悉了正则表达式,也掌握了各种java语法,熟悉了输入输出语句,了解存储结构arrary、arraylist、list等的使用,类的封装、调用规则。总而言之,虽然这三次程序设计难度很大,遇到很多问题,但也让我收获满满。