答题判题程序1-3题目集总结

(目录可以点击侧边提示使用哦😊😊😊)
目录
1. 前言
1.1答题判题程序-1知识点、题量、难度分析
1.2答题判题程序-2知识点、题量、难度分析
1.3答题判题程序-3知识点、题量、难度分析
2. 设计与分析
2.1答题判题程序-1设计与分析
2.2答题判题程序-2设计与分析
2.3答题判题程序-3设计与分析
3. 采坑心得
3.1答题判题程序-1采坑心得
3.2答题判题程序-2采坑心得
3.3答题判题程序-3采坑心得
4. 改进建议
4.1答题判题程序-1改进建议
4.2答题判题程序-2改进建议
4.3答题判题程序-3改进建议
5. 总结
5.1本阶段三次题目集收获
5.2深入学习探究的思考
5.3问题改进与建议
6. 答题判题程序-3代码节选分析、顺序图
1. 前言
1.1答题判题程序-1知识点、题量、难度分析

一、面向对象编程
(一)类的定义与封装
(1)类的定义:代码中定义了Question(题目类)、Paper(试卷类)、AnswerTest(答卷类)、Main(主类)。每个类都有明确的属性和方法,体现了面向对象编程中封装的思想。
- 例如,Question类封装了题目编号number、内容content、标准答案standardAnswer,通过构造函数和访问器方法来设置和获取这些属性。
点击查看代码
class Question {
private int number;
private String content;
private String standardAnswer;
//...
}
点击查看代码
public int getNumber() {
return number;
}
(二)构造函数
(1)有参构造函数:每个类都有一个有参构造函数,用于初始化对象的属性。构造函数在创建对象时被调用,确保对象在创建后具有正确的初始状态。
- 例如,Question类的构造函数接受题目编号、内容和标准答案作为参数,并将这些参数赋值给相应的属性。
点击查看代码
//有参构造题目
public Question(int number, String content, String standardAnswer) {
this.number = number;
this.content = content;
this.standardAnswer = standardAnswer;
}
paper.SaveQuestion(number - 1, new Question(number, content, standardAnswer));
二、集合的使用
(一)ArrayList
(1)定义和初始化:代码中使用了ArrayList集合来存储题目列表、答案列表和判题结果列表。ArrayList是 Java 中一种动态数组,可以方便地添加和删除元素。
- 例如,在Paper类中,定义了一个List
类型的questions属性,并在构造函数中初始化它为一个新的ArrayList。
点击查看代码
class Paper {
List<Question> questions;
private int questionCount;
public Paper(int questionCount) {
this.questionCount = questionCount;
questions = new ArrayList<>();
}
}
(2)添加和获取元素:通过add方法向ArrayList中添加元素,通过索引访问ArrayList中的元素。在代码中,使用ArrayList来存储题目对象、答案和判题结果,并在需要时进行添加和获取操作。
- 例如,在Paper类的SaveQuestion方法中,使用while循环和add方法确保questions列表的大小足够容纳指定的题目编号,然后通过set方法将题目对象保存到指定位置。
点击查看代码
while (questions.size() <= number) {
questions.add(null);
}
questions.set(number, question);
三、字符串处理
(一)字符串提取
(1)使用索引和截取方法:代码中使用了字符串的索引和substring方法来提取题目编号、内容和标准答案。通过确定特定字符串在输入字符串中的位置,然后使用substring方法截取相应的部分。
- 例如,在提取题目编号时,使用indexOf方法找到特定字符串的位置,然后使用substring方法截取题目编号部分。
int number = Integer.parseInt(questionLine.substring(questionLine.indexOf(":") + 1, questionLine.indexOf(" #Q:")).trim());
(2)去除空格:在Question类的getContent方法中,通过循环从后向前遍历字符串,找到第一个非空格字符的位置,然后提取出子字符串,去除了末尾的空格。
(二)字符串处理方法
replaceAll 和 split 方法:在处理答案字符串时,使用replaceAll方法去除特定字符串,然后使用split方法将字符串分割成数组。
- 例如,在分离答案时,使用replaceAll方法去除 “#A:”,然后使用trim方法去除两端的空格,最后使用split方法根据空格分割字符串。
String[] answers = answerLine.replaceAll("#A:", "").trim().split("\\s+");
四、输入输出操作
(一)Scanner 类
读取用户输入:使用Scanner类从控制台读取用户输入。通过input.nextLine()方法读取一行输入,并对输入的字符串进行解析和处理。
- 例如,读取题目数量和题目信息,以及用户输入的答案。
点击查看代码
Scanner input = new Scanner(System.in);
int questionCount = Integer.parseInt(input.nextLine());
(二)输出结果
格式化输出:使用System.out.println输出处理后的结果。通过String.join方法将列表中的元素连接成一个字符串进行输出,方便用户查看答题结果。
- 例如,输出答案列表和判题结果列表。
System.out.println(String.join("\n", answerOut));
五、控制流程
(一)循环结构
(1)for 循环:使用for循环来处理题目信息的输入和答案的保存。在读取题目信息时,通过循环逐行读取输入,并提取题目编号、内容和标准答案,然后保存到试卷对象中。在处理用户输入的答案时,使用循环遍历答案数组,将每个答案保存到答卷对象中。
-
例如,处理答案的循环。
for (int i = 0; i < answers.length; i++) { answerSheet.SaveOneAnswer(i, answers[i].trim()); }
(2)while 循环:使用while循环来读取用户输入的答案,直到输入 “end” 结束循环。
String answerLine;
while (!(answerLine = input.nextLine()).equals("end")) { //... }(二)条件判断
使用条件判断来确定循环的结束条件(当输入为 “end” 时结束循环),以及在处理答案列表和判题结果列表时,使用条件判断来确保索引不越界。
for (int i = 0; i < questionCount; i++) { //... }
(一)题目数量的灵活性
(1)题目数量由用户输入的第一个整数确定,可以是任意正整数,这使得题量具有很大的灵活性。
(2)无论是单个题目还是多个题目,程序都能正确处理。
(二)题目内容的多样性
(1)题目内容可以是各种数学运算、常识问题等,没有固定的题型限制。
(2)输入格式中对题目内容的格式有明确规定,包括题号、题目内容和标准答案的标识,但题目内容本身可以包含各种字符和表达式,如输入样例中的题目内容包含空格和数学运算符号。
(三)答题信息的复杂性
(1)答题信息根据题目数量进行输入,每个答案对应一道题目,答案之间以英文空格分隔。
(2)答题信息的输入以 “end” 标记结束,程序需要识别这个结束标志,并忽略之后的输入信息,增加了程序处理输入的复杂性。
(一)输入格式处理
题目数量:题目数量为整数数值,且最高位不能为 0(若超过 1 位),这要求在读取输入时进行合法性检查,增加了一定的难度。
题目内容:题目内容的格式较为复杂,包含题号、题目内容和标准答案的特定标识。同时,题目输入顺序与题号不相关,不一定按题号顺序从小到大输入,这需要程序能够正确解析和存储题目信息,按照题号进行整理。
答题信息:答题信息要求答案数量与题目数量相同,且答案之间以英文空格分隔,以 “end” 标记结束。这需要程序能够正确读取和解析每行的答案信息,进行存储和判题。对于含多余空格符的情况,也需要进行适当的处理,增加了输入处理的复杂性。
(二)类的设计与实现
题目类:题目类需要封装题目编号、题目内容和标准答案等信息,并提供数据读写方法和判题方法。判题方法需要准确判断输入的答案是否符合标准答案,这涉及到字符串的比较和处理。
试卷类:试卷类要管理整套题目的信息,包括题目列表和题目数量。保存题目方法需要根据题号将题目正确地保存到题目列表中,判题方法需要根据题号和答案判断是否符合对应题目的标准答案,这要求对集合的操作和索引的管理有较好的理解。
答卷类:答卷类要封装答题信息,包括试卷对象、答案列表和判题结果列表。保存答案方法需要根据题号保存答题结果,判题方法需要判断答案列表中特定题号的结果是否符合试卷中对应题目的标准答案,输出方法需要按照特定格式输出题目内容和答题结果。这涉及到多个类之间的交互和数据的传递,增加了程序的复杂性。
(三)边界情况和异常处理
题目集中存在多种边界情况,例如单个题目和多个题目的处理、题号顺序与输入顺序不同的情况、含多余空格符的情况等。程序需要能够正确处理这些边界情况,确保在各种情况下都能输出正确的结果。
还需要进行适当的异常处理,例如输入不是有效的整数、题目内容格式错误、答题信息数量与题目数量不匹配等情况。异常处理需要考虑到程序的稳定性和用户体验,增加了编程的难度。
1.2答题判题程序-2知识点、题量、难度分析

- 注明:该题所涉及面向对象编程部分的知识点,包括有类的定义、构造函数,以及字符串处理的知识点包括字符串提取时使用索引和截取方法、使用trim方法去除字符串两端的空格,与第一题理论知识一致,此处不再赘述。
一、面向对象编程
(一)类和对象
类定义:Question、TestPaper、AnswerSheet 和 Main 是类的定义。
对象实例化:使用 new 关键字创建类的实例。
TestPaper testPaper = new TestPaper(1);
(二)封装
私有成员变量:使用 private 关键字声明成员变量,以限制直接访问。
公共访问器和修改器:提供公共的 getter 和 setter 方法来访问和修改私有变量。
点击查看代码
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
二、集合的使用
(一)Map 和 List 的运用
(1)Map 的定义和初始化:代码中使用了Map来存储试卷和答卷对象,以及题目对象。Map是一种键值对的数据结构,可以方便地根据键来查找对应的值。
- 例如,在主类中,定义了Map<Integer, TestPaper>类型的testPapers变量,用于存储不同编号的试卷对象,使用泛型来确保类型的安全。。
Map<Integer, TestPaper> testPapers = new HashMap<>();
(2)List 的定义和初始化:代码中还使用了List来存储各种信息,如题目编号列表、答卷格式化后的答案列表等。List是一种有序的集合,可以方便地添加和访问元素。 - 例如,在TestPaper类中,定义了Map<Integer, Integer>类型的scores变量,用于存储题目编号和对应的分数,然后在getAlerts方法中,使用List
类型的alerts变量来存储警示信息。
点击查看代码
class TestPaper {
//...
Map<Integer, Integer> scores = new HashMap<>();
List<String> getAlerts() {
List<String> alerts = new ArrayList<>();
//...
return alerts;
}
}
(3)添加和获取元素:通过put方法向Map中添加键值对,通过get方法根据键获取对应的值。
在List中,通过add方法添加元素,通过索引访问元素。在代码中,使用Map和List来存储和管理各种信息,并在需要时进行添加和获取操作。
- 例如,在TestPaper类的addScore方法中,向scoresMap 中添加题目编号和对应的分数。在AnswerSheet类的getFormattedAnswers方法中,使用List
类型的questionNumbers变量来存储题目编号,然后根据题目编号获取题目内容、答案和判题结果,将它们组合成格式化的字符串添加到formatted列表中。
点击查看代码
//...
public void addScore(int questionNumber, int score) {
scores.put(questionNumber, score);
}
//...
public List<String> getFormattedAnswers() {
List<String> formatted = new ArrayList<>();
//...
formatted.add(content + "~" + answer + "~" + (isCorrect? "true" : "false"));
}
//...
(二)数组
数组声明和初始化:声明和初始化数组。本题用于记录第i张答卷的答卷编号。
int[] test_Number = new int[5];
三、字符串处理
(一)字符串提取
(1)字符串分割:在处理试卷分数信息时,使用了字符串的split方法将输入的字符串按照特定的分隔符分割成数组。然后对数组中的每个元素进行进一步处理,提取题目编号和分数。
- 例如,在TestPaper类的构造函数中,使用split方法将输入的字符串按照空格分割成多个部分,然后对每个部分再按照 “-” 分割,提取题目编号和分数。
点击查看代码
String[] parts = line.substring(3).split(" ");
for (String score : parts[1].split("\\s+")) {
String[] scoreParts = score.split("-");
int questionNumber = Integer.parseInt(scoreParts[0].trim());
int scoreValue = Integer.parseInt(scoreParts[1].trim());
//...
}
(二)字符串操作
substring:提取字符串的子串。
charAt:获取字符串中指定位置的字符。
equals:比较两个字符串是否相等。
点击查看代码
String result = content.substring(0, endIndex);
char str = result.charAt(0);
if (str == ' ') return result.substring(1);
else return result;
(三)Lambda表达式
Lambda表达式允许以更简洁的方式编写实例化接口的代码。Lambda表达式主要用于实现只有一个抽象方法的接口,这种接口称为函数式接口(Functional Interface)。
Lambda表达式的一般语法:
(parameters) -> expression或者(parameters) -> { statements; }
parameters:参数列表,如果只有一个参数且其类型可推断,则可以省略参数类型和括号。
->:读作“前往”,是Lambda表达式的标志。
expression:如果Lambda体只有一条语句,并且返回值是这个表达式的结果,则可以省略花括号和return关键字。
statements:如果Lambda体包含多条语句,则需要包含在花括号中,并需要显式返回值。
方法引用:是Lambda表达式的另一种形式,它直接引用现有的方法。
方法引用的一般语法:
instance::method:对特定对象的实例方法的引用。
ClassName::staticMethod:对静态方法的引用。
ClassName::instanceMethod:对任意对象的实例方法的引用。
- 例如,split("#A:"):根据"#A:"分割字符串。stream():将数组转换为流。skip(1):跳过第一个元素。map(s -> s.trim()):使用Lambda表达式,将每个字符串元素传递给trim()方法。toArray(String[]::new):使用方法引用,将流转换回数组。这里String[]::new是方法引用,它引用了String数组的构造器。通过这种方法提取出答卷中的答案。
String[] answers = Arrays.stream(a[1].split("#A:")).skip(1).map(s -> s.trim()).toArray(String[]::new);;
四、控制流程
(一)循环结构
(1)while 循环:使用while循环来读取用户输入的多行数据,直到输入为空行或 “end” 结束循环。在循环中,对每一行输入进行解析和处理,根据输入的内容创建题目、试卷或答卷对象。
- 例如,在主类的main方法中,使用while (scanner.hasNextLine())循环来读取用户输入,对输入的每一行进行处理。
点击查看代码
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty() || line.equals("end")) {
break;
}
//...
}
(2)for 循环:在处理答卷信息和输出格式化的答案列表时,使用了for循环来遍历题目编号列表、答案列表和判题结果列表等。在循环中,对每个元素进行处理,将题目内容、答案和判题结果组合成格式化的字符串输出。
- 例如,在AnswerSheet类的getFormattedAnswers方法中,使用for循环遍历题目编号列表,根据题目编号获取题目内容、答案和判题结果,将它们组合成格式化的字符串添加到列表中。
(二)条件判断
(1)在方法中使用条件判断:在多个方法中使用了条件判断语句来处理各种情况。例如,在TestPaper类的getQuestionContent方法中,判断题目是否存在,如果不存在则返回 null。在AnswerSheet类的addAnswer方法中,判断答案是否为 null,如果为 null 则将答案设置为 null。
- 例如,在TestPaper类的getQuestionContent方法中:
点击查看代码
public String getQuestionContent(int questionNumber) {
Question question = questions.get(questionNumber);
if(question!=null)
return question.getContent();
else
return null;
}
(2)在主方法中使用条件判断:在主方法中,使用条件判断来确定输入的内容是题目、试卷还是答卷信息,并进行相应的处理。还使用条件判断来检查试卷和答卷是否存在,以及输出相应的提示信息。
- 例如,在主方法中,根据输入的字符串的开头判断是题目、试卷还是答卷信息:
点击查看代码
if (line.startsWith("#N:")) {
// 处理题目信息
} else if (line.startsWith("#T:")) {
// 处理试卷信息
} else if (line.startsWith("#S:")) {
// 处理答卷信息
}
五、其他
(一)computeIfAbsent方法
该方法属于Map接口。这个方法的作用是:如果指定的键(key)不存在于Map中,则计算其值,并将其添加到Map中。如果键已经存在,则返回其对应的值。
- 例如
TestPaper testPaper = testPapers.computeIfAbsent(testNumber, k -> new TestPaper(k));
computeIfAbsent方法首先检查testPapers Map中是否已经包含了键testNumber。
如果testPapers中已经有了testNumber这个键,computeIfAbsent方法将返回与该键关联的值(即TestPaper对象),而不会执行Lambda表达式。
如果testPapers中没有testNumber这个键,computeIfAbsent方法将执行Lambda表达式k -> new TestPaper(k),创建一个新的TestPaper对象,并将这个新对象与键testNumber一起添加到testPapers Map中。
不管是返回已有的值,还是添加新创建的值,computeIfAbsent方法都会返回与键testNumber关联的TestPaper对象的引用。
(二)检查原卷testPaper对象中的题目问题列表questions映射是否已经包含了特定的题目序号questionNumber作为键。
点击查看代码
if (!testPaper.questions.containsKey(questionNumber)) {
testPaper.addQuestion(questionNumber, allQuestions.get(questionNumber));
}
首先,代码检查testPaper.questions映射是否已经包含了questionNumber作为键。
如果不包含,说明这个问题还没有被添加到测试试卷中。
然后,代码尝试从allQuestions映射中获取与questionNumber对应的Question对象。
最后,如果找到了对应的Question对象,就调用addQuestion方法将其添加到testPaper.questions映射中。
一、题目类型与数量的不确定性
(一)题目信息
(1)题目数量不固定:输入的题目信息可以有多行,代表多道题目。题目的输入顺序与题号不相关,且允许题目编号有缺失。
(2)样例展示多种情况:例如输入样例中展示了不同数量的题目输入,从简单的两道题到包含更多题目的情况,涵盖了各种可能的题目数量变化。
(二)试卷信息
(1)试卷数量不固定:可以输入多行试卷信息,代表多张试卷。这意味着程序需要能够处理任意数量的试卷,增加了题量的不确定性。
(2)题目组合多样:每张试卷中的题目编号和分值各不相同,且一行信息中可有多项题目编号与分值。
(三)答卷信息
(1)答卷数量不固定:答题信息按行输入,每一行为一张答卷的答案,可输入多行代表多张答卷。
(2)答案数量与试卷题目数量关系不确定:答案数量可以不等于试卷信息中题目的数量,没有答案的题目计 0 分,多余的答案直接忽略。
二、题量变化对程序的影响
(一)数据存储与处理
(1)动态存储需求:由于题目、试卷和答卷的数量都不确定,程序需要使用动态的数据结构,如列表或映射,来存储和管理这些信息。
(2)复杂的逻辑处理:程序需要根据不同数量的题目、试卷和答卷进行各种逻辑判断和处理。例如,判断试卷总分是否为 100 分、处理不同数量的答案与题目对应关系等。
(二)输出内容的变化
(1)输出行数不确定:根据输入的题目、试卷和答卷数量,输出的行数也会相应变化。例如,答卷信息和判分信息会根据答卷数量重复输出,试卷总分警示也只有在特定情况下才会输出。
(2)输出内容的复杂性:输出的内容不仅包括题目内容、答案和判题结果,还包括试卷总分警示和判分信息。
一、输入处理的复杂性
(一)多种信息混合输入
挑战:程序需要处理题目信息、试卷信息和答卷信息三种可能打乱顺序混合输入的情况。这要求程序能够准确识别不同类型的输入,并将其正确地存储和关联起来。
举例:在输入样例中,各种信息的输入顺序是随机的,程序需要在读取每一行输入时判断是题目信息、试卷信息还是答卷信息,并进行相应的处理。例如,当读取到 “#N:1 #Q:1+1= #A:2” 时,程序要识别这是题目信息,并将其存储到相应的数据结构中;当读取到 “#T:1 1-5 2-8” 时,要识别这是试卷信息,并建立试卷与题目之间的关联。
(二)题目编号的不确定性
挑战:题目编号不按顺序输入,且允许有缺失。这增加了程序在处理题目信息时的难度,需要设计合适的数据结构来存储题目,以便能够快速地根据题目编号查找题目内容和标准答案。
举例:在输入样例中,题目编号可能不是连续的,如 “#N:1 #Q:1+1= #A:2”“#N:2 #Q:2+2= #A:4”“#N:3 #Q:3+2= #A:5”,但也可能存在缺失的编号,如没有编号为 4 的题目。程序在处理题目信息时,需要考虑到这种不确定性,不能假设题目编号是连续的。
二、输出生成的复杂性
(一)试卷总分警示
挑战:程序需要判断每张试卷的总分是否为 100 分,如果不是,则输出相应的警示信息。这要求程序在处理试卷信息时,能够准确地计算试卷的总分,并根据总分情况决定是否输出警示信息。
举例:在输入样例中,有些试卷的总分不等于 100 分,如 “#T:1 1-70 2-30”,程序需要输出 “alert: full score of test paper1 is not 100 points”。而对于总分等于 100 分的试卷,则不需要输出警示信息。
(二)答卷信息和判分信息的生成
挑战:程序需要根据答卷信息和试卷信息生成答题信息和判分信息。这涉及到对答案的正确性判断、得分的计算以及输出格式的控制。
举例:对于输入样例 “#S:1 #A:5 #A:22”,程序需要根据试卷信息找到对应的题目,判断答案的正确性,如 “1+1=5false” 表示第一题答案错误;同时,计算每道题的得分,如 “70 0~70” 表示第一题得 70 分,第二题得 0 分,总分 70 分。输出的格式也有严格的要求,需要按照 “题目内容 +‘~’+ 答案 +‘~’+ 判题结果(true/false)” 和 “题目得分 +‘ ’+…+ 题目得分 +‘~’+ 总分” 的格式输出。
(三)错误试卷号的处理
挑战:如果答案信息中的试卷号找不到,程序需要输出 “the test paper number does not exist”。这要求程序在处理答卷信息时,能够检查试卷号的有效性,并在试卷号无效时输出相应的错误提示。
举例:在输入样例 “#S:3 #A:5 #A:4” 中,由于没有输入试卷号为 3 的试卷信息,程序需要输出 “The test paper number does not exist”。
三、类设计的复杂性
(一)增加答题类
挑战:设计建议中要求增加答题类,类的内容以及类之间的关联需要自行设计。这增加了程序设计的复杂性,需要考虑答题类与题目类、试卷类之间的关系,以及如何在答题类中实现对答案的处理和判分功能。
举例:答题类可能需要存储答卷信息、关联对应的试卷、判断答案的正确性、计算得分等功能。同时,需要考虑如何在主程序中正确地使用答题类,以及如何处理答题类与其他类之间的交互。
1.3答题判题程序-3知识点、题量、难度分析

-
注明:该题所涉及面向对象编程部分的知识点,包括有类的定义和封装、构造函数,以及字符串处理的知识点,while 循环、for 循环、条件判断的相关知识点,与前两题理论知识一致,此处不再赘述。
一、字符串处理
字符串匹配与验证
使用正则表达式进行字符串匹配:代码中使用正则表达式来验证输入的题目、试卷和学生信息的格式是否正确。如果输入的格式不正确,则输出错误提示信息。 -
例如,在处理题目信息#N:1 #Q:1+1= #A:2时,使用正则表达式"#N:\d+\s#Q:.+\s#A:.+"来验证输入的格式是否正确。目的是匹配格式为 #N:<数字> #Q:<问题内容> #A:<答案> 的字符串。其中:#N: 表示字符串以 #N: 开头。
\d+ 匹配一个或多个数字(问题编号)。
\s 匹配空格字符。#Q: 表示问题内容的开始。
.+ 匹配任意数量的任意字符(问题内容)。#A: 表示答案的开始。
.+ 再次匹配任意数量的任意字符(答案)。
String shunxu="#N:\\d+\\s#Q:.+\\s#A:.+";//匹配问题 -
例如,在处理试卷信息#T:1 1-5 2-8时,使用正则表达式"#T:\d(\s(\d+-\d+))*"来验证输入的格式是否正确。
String test="#T:\\d(\\s(\\d+-\\d+))*";//匹配试卷
二、其他
(一).matches() 方法
是一个字符串(String)类的方法,它用于判断整个字符串是否与给定的正则表达式匹配。如果字符串完全匹配正则表达式,则返回 true;否则返回 false。
- 例如
点击查看代码
String shunxu="#N:\\d+\\s#Q:.+\\s#A:.+";//匹配问题
if (!line.matches(shunxu)) {
System.out.println("wrong format:" + line);
continue;
}
(二).contains() 方法
是一个集合(Collection)和映射(Map)接口中的方法,它用于检查集合或映射中是否包含指定的元素或键。
对于List、Set和Map的实现类,.contains() 方法的实现细节可能有所不同,但基本功能是一致的:
List.contains(Object o):检查列表中是否包含指定的元素。
Set.contains(Object o):检查集合中是否包含指定的元素。
Map.containsKey(Object key):检查映射中是否包含指定的键。
if (paper.deletedQuestions.contains(questionNumber)) { formatted.add("the question " + questionNumber + " invalid" + "~0"); }
代码使用 .contains() 方法来检查 paper.deletedQuestions 集合是否包含特定的 questionNumber。如果集合包含这个编号,说明这个问题已经被标记为删除,因此代码会向 formatted 列表中添加一条表示该问题无效的消息,并附上分数 0。
一、输入信息类型与数量的不确定性
(一)题目信息
(1)题目数量不固定
(2)题目输入格式不固定
(二)试卷信息
(1)试卷输入格式不固定
(2)题目组合多样:每张试卷中的题目编号和分值各不相同,且一行信息中可有多项题目编号与分值。
(三)学生信息
学生数量不固定:学生信息只输入一行,包含多个学生的信息,增加了程序处理学生信息的复杂性。
(四)答卷信息
答案数量与试卷题目数量关系不确定:答案数量可以不等于试卷信息中题目的数量,没有答案的题目计 0 分,多余的答案直接忽略。这种不确定性增加了程序处理答卷信息的复杂性。
(五)删除题目信息
(1)删除题目数量不固定:删除题目信息可以有多行,代表多个题目被删除。这使得删除题目的数量具有不确定性。
(2)对试卷的影响:删除题目后,引用该题目的试卷依然有效,但被删除的题目将以 0 分计,同时在输出答案时需要进行相应的提示。这增加了程序处理试卷和答案信息的复杂性。
二、题量变化对程序的影响
(一)数据存储与处理
动态存储需求:由于题目、学生、删除题目信息的数量都不确定,程序需要使用动态的数据结构,如列表或映射,来存储和管理这些信息。这增加了程序在数据存储和处理方面的难度。
复杂的逻辑处理:程序需要根据不同数量的题目、试卷、学生、答卷和删除题目信息进行各种逻辑判断和处理。例如,判断试卷总分是否为 100 分、处理不同数量的答案与题目对应关系、处理删除题目后的试卷和答案输出等。
(二)输出内容的变化
(1)输出行数不确定:根据输入的题目、试卷、学生、答卷和删除题目信息的数量,输出的行数也会相应变化。例如,答卷信息和判分信息会根据答卷数量重复输出,试卷总分警示也只有在特定情况下才会输出。
(2)输出内容的复杂性:输出的内容不仅包括题目内容、答案和判题结果,还包括试卷总分警示、判分信息、被删除的题目提示信息、题目引用错误提示信息、格式错误提示信息、试卷号引用错误提示信息和学号引用错误提示信息等。这些输出内容需要根据不同的输入情况进行动态生成,增加了程序的复杂性。
一、输入格式的复杂性
(一)多种信息类型混合输入
信息种类繁多:程序需要处理题目信息、试卷信息、学生信息、答卷信息和删除题目信息五种不同类型的输入。
输入顺序不确定:这些信息可能会打乱顺序混合输入,程序需要能够正确识别不同类型的信息,并按照相应的逻辑进行处理。
(二)格式约束严格
题目信息:题目编号不一定按顺序输入,且允许编号有缺失。题目内容和标准答案的格式也有特定要求,需要进行字符串处理和验证。
试卷信息:题目编号应与题目信息中的编号对应,一行信息中可有多项题目编号与分值。
学生信息:学生信息只在一行中输入,包含多个学生的学号和姓名,需要进行字符串分割和处理。
答卷信息:答案数量可以不等于试卷信息中题目的数量,答案内容可能为空,且首尾有多余空格时需要去除。还需要注意顺序号与题号的区别,增加了处理的复杂性。
删除题目信息:格式特定,需要准确识别题目号并进行相应的处理。
二、输出格式的多样性和条件判断
(一)多种输出内容
试卷总分警示:仅在试卷总分不等于 100 分时输出,需要计算试卷总分并进行判断。
答卷信息:根据试卷的题目数量输出多行数据,包括题目内容、答案和判题结果。需要进行字符串拼接和判题逻辑处理。
判分信息:输出答题记录所对应试卷的每道小题的计分以及总分,计分输出的先后顺序与题目题号相对应。涉及到分数计算和输出格式的控制。
被删除的题目提示信息:当题目被删除时,需要在答案中输出特定的提示信息。
题目引用错误提示信息:试卷错误引用不存在题号的试题时,输出相应的提示。
格式错误提示信息:对不符合格式要求的输入进行错误提示。
试卷号引用错误提示输出和学号引用错误提示信息:分别处理试卷号和学号引用错误的情况。
(二)复杂的条件判断
多种情况的判断:程序需要根据不同的输入情况进行多种条件判断,例如判断答案数量与试卷题目数量的关系、处理删除题目后的输出、判断题目引用是否正确等。
优先级判断:当一道题目同时出现答案不存在、引用错误题号、题目被删除等情况时,需要确定优先级并输出相应的提示信息。
三、数据存储和管理
(一)动态数据结构的使用
不确定的数量:由于题目、学生、删除题目信息的数量都不确定,程序需要使用动态的数据结构,如列表或映射,来存储和管理这些信息。这增加了数据存储和访问的复杂性。
信息关联:不同类型的信息之间存在关联,例如试卷信息中的题目编号需要与题目信息中的编号对应,答卷信息需要与试卷信息和学生信息相关联。程序需要正确维护这些关联关系,确保信息的一致性和准确性。
四、错误处理和异常情况
(一)格式错误处理
严格的格式要求:输入信息只要不符合格式要求,均需要输出错误提示信息。这要求程序能够准确识别各种格式错误,并提供清晰的错误提示。
多种错误情况:包括题目格式错误、试卷格式错误、学生信息格式错误、答卷格式错误等。程序需要针对不同类型的错误进行相应的处理,增加了错误处理的难度。
(二)异常情况处理
删除题号不存在的情况暂不考虑:虽然题目中说明暂不考虑删除的题号不存在的情况,但在实际编程中,需要考虑未来可能出现的这种情况,并设计相应的处理机制。
多张答卷的情况暂不考虑:同样,虽然题目中暂不考虑出现多张答卷的信息的情况,但程序需要具备扩展性,以便在未来需要处理多张答卷时能够进行相应的调整。
2. 设计与分析

2.1答题判题程序-1设计与分析
2.1.1.答题判题程序-1的PowerDesigner的uml类图
🎨🎨🎨

类和属性说明:
- 类名
Question:
属性:
number (int):题目编号。
content (String):题目内容。
standardAnswer (String):标准答案。
方法:
Question(int number, String content, String standardAnswer):构造函数。
getNumber():返回题目编号。
getContent():返回题目内容。
getStandardAnswer():返回标准答案。
CheckOneAnswer(String answer):检查给定答案是否与标准答案匹配。 - 类名
Paper:
属性:
questions (List):题目列表。
questionCount (int):题目数量。
(方法)
Paper(int questionCount):构造函数。
SaveQuestion(int number, Question question):保存题目。
CheckAnswerAndStandar(int number, String answer):检查答案是否符合标准答案。 - 类名
AnswerTest:
属性:
paper (Paper):试卷对象。
answerslist (List):答案列表。
JudgeResults (List):判题结果列表。
方法:
AnswerTest(Paper paper):构造函数。
SaveOneAnswer(int number, String answer):保存一个答案。
judge(int number):判断答案是否正确。 - 类名
Main:
方法:
main(String[] args):主函数,程序入口。
关系说明:
聚合关系(Paper --> Question):Paper 类包含 Question 对象。
依赖关系(Main --> Paper, AnswerTest, Question):Main 类使用 Paper、AnswerTest 和 Question 类。
关联关系(AnswerTest --> Paper):AnswerTest 类使用 Paper 类。
2.1.2答题判题程序-1的SourceMonitor的生成报表内容

具体参数分析
项目信息:
Project Directory(项目目录)C:LanQiaoTestproject\Tast_Java。
Project Name(项目名称):blog1。
Checkpoint Name(检查点名称):Baseline。
文件信息:
File Name(文件名):Main.java。
Lines(代码行数):147。
Statements(语句数量):82。
Percent Branch Statements(分支语句占比):22.0%。
Method Call Statements(方法调用语句数量):74。
Percent Lines with Comments(带注释的行数占比):30.6%。
Classes and Interfaces(类和接口数量):3。
Methods per Class(平均每个类的方法数量):1.33。
Average Statements per Method(平均每个方法的语句数量):12.50。
复杂度相关:
Name of Most Complex Method(最复杂方法名称):Main.main ()。
Maximum Complexity(最大复杂度):1。
Line Number of Deepest Block(最深代码块所在行号):114。
Maximum Block Depth(最大代码块深度):3。
Average Block Depth(平均代码块深度):0.85。
Average Complexity(平均复杂度):1.00。
复杂方法详情:
Most Complex Methods in 1 Class (es)(1 个类中的最复杂方法):Complexity(复杂度)为 1,Statements(语句数量)为 8,Max Depth(最大深度)为 2,Calls(调用次数)为 18,对应的方法是 Main.main ()。
- 由以上数据可知,我的第一题代码优劣:
- 注释覆盖率:
带注释的行数占比为30.6%,这是一个相对较高的比例,表明代码有较好的注释覆盖,有助于提高代码的可读性和可维护性。 - 代码行数与方法数量:
文件中有147行代码,平均每个类有1.33个方法,这意味着每个方法大约有110.5行代码。这个数字相对较高,可能表明方法过于庞大,难以维护。 - 代码复杂度:
最复杂方法的复杂度为1,这是一个非常低的数值,表明代码的复杂度不高,逻辑相对简单。
最复杂方法的最大代码块深度为3,平均代码块深度为0.85,这表明方法的嵌套结构不深,有助于代码的理解和维护。 - 分支语句和方法调用:
分支语句占比为22.0%,这个比例适中,表明代码中有适量的逻辑分支,不会过于复杂。
方法调用语句数量为74,相对较多,这可能表明代码中有较多的方法交互,但也可能增加理解和维护的难度。 - 类和接口数量:
项目中有3个类和接口,这个数量适中,有助于代码的模块化。 - 最复杂方法:
Main.main()方法是最复杂的,复杂度为1,这通常是一个好的迹象,因为main方法作为程序的入口点,应该尽可能简单,调用其他方法来执行具体的任务。 - 平均每个方法的语句数量:
平均每个方法的语句数量为12.50,这个数字相对较低,表明方法可能相对简单,易于理解和维护。
2.2答题判题程序-2设计与分析
2.2.1.答题判题程序-2的PowerDesigner的uml类图
🎨🎨🎨

类和属性说明:
- 类名
Question:
属性:
int number:题目编号。
String content:题目内容。
String standardAnswer:标准答案。
方法:
Question(int number, String content, String standardAnswer):构造函数。
int getNumber():返回题目编号。
String getContent():返回处理后的题目内容。
String getStandardAnswer():返回标准答案。
boolean checkAnswer(String answer):检查给定答案是否正确。 - 类名
TestPaper:
属性:
int number:试卷编号。
Map<Integer, Question> questions:存储题目编号和题目对象的映射。
Map<Integer, Integer> scores:存储题目编号和分数的映射。
int maxScore:试卷总分。
方法:
TestPaper(int number):构造函数。
void addQuestion(int questionNumber, Question question):添加题目。
void addScore(int questionNumber, int score):添加题目分数。
String getQuestionContent(int questionNumber):获取题目内容。
boolean isTotalScoreCorrect():检查总分是否正确。
ListgetAlerts():获取警告信息。
ListgetQuestionNumbers():获取所有题目编号。
int getScore(int questionNumber):获取题目分数。
boolean checkAnswer(int questionNumber, String answer):检查答案是否正确。 - 类名
AnswerSheet:
属性:
TestPaper paper:关联的试卷对象。
Listanswers:存储答案的列表。
Listresults:存储答案正确与否的列表。
Listscores:存储分数的列表。
方法:
AnswerSheet(TestPaper paper):构造函数。
void clearAnswer():清空答案。
void addAnswer(int number, String answer):添加答案。
void judgeAnswers():判断答案正确性并计算分数。
ListgetFormattedAnswers():获取格式化后的答案。
String getScoreResults():获取分数结果。 - 类名
Main:
方法:
main(String[] args):主函数,程序入口。
关系说明
关联(Association):
TestPaper 包含 Question 对象,表示一个试卷包含多个题目。
AnswerSheet 使用 TestPaper 对象,表示一个答卷对应一个试卷。
依赖(Dependency):
Main 类依赖 Question、TestPaper 和 AnswerSheet 类,表示主程序中创建和操作这些对象。
聚合(Aggregation):
TestPaper 聚合 Question 对象,表示试卷由多个题目组成,但题目也可以独立于试卷存在。
2.2.2答题判题程序-2的SourceMonitor的生成报表内容


具体参数分析
项目信息:
Project Directory(项目目录):C:LanQiaoTestprojectiTast_Javal。
Project Name(项目名称):blog2。
Checkpoint Name(检查点名称):Baseline。
文件信息:
File Name(文件名):Main.java。
Lines(代码行数):392。
Statements(语句数量):258。
Method Call Statements(方法调用语句数量):195。
Percent Branch Statements(分支语句占比):25.6%。
Percent Lines with Comments(带注释的行数占比):3.6%。
Classes and Interfaces(类和接口数量):4。
Average Statements per Method(平均每个方法的语句数量):5.25。
Methods per Class(平均每个类的方法数量):9.00。
复杂度相关:
Name of Most Complex Method(最复杂方法名称):Main.main ()。
Maximum Complexity(最大复杂度):3.57。
Line Number of Deepest Block(最深代码块所在行号):225。
Maximum Block Depth(最大代码块深度):7。
Average Block Depth(平均代码块深度):273。
复杂方法详情:
Most Complex Methods in 4 Class (es)(4 个类中的最复杂方法)分别列出了多个方法的复杂度、语句数量、最大深度和调用次数,如 AnswerSheet.addAnswer ()(4, 6,3,6)、AnswerSheet ()(1,4,2,3)、AnswerSheet.clearAnswer ()(2,2,3,3)等。
- 由以上数据可知,我的第二题代码优劣:
- 注释覆盖率:
带注释的行数占比为3.6%,这个比例非常低。良好的代码应该有足够的注释来帮助理解代码的意图和逻辑,尤其是在复杂或重要的部分。
代码行数与方法数量:
文件中有392行代码,平均每个类有9个方法,这意味着每个方法大约有43.56行代码。这个数字相对较高,可能表明方法过于庞大,难以维护。 - 代码复杂度:
最复杂方法的复杂度为3.57,这个数值相对较低,表明代码的复杂度不高。
最复杂方法的最大代码块深度为7,平均代码块深度为273,这里可能存在一个错误,因为平均代码块深度通常不会这么高。如果这个数字是正确的,那么它表明方法的嵌套结构非常深,这可能导致代码难以理解和维护。 - 分支语句和方法调用:
分支语句占比为25.6%,这个比例相对较高,表明代码中有较多的逻辑分支,这可能增加代码的复杂性。
方法调用语句数量为195,这表明代码中有很多方法之间的交互,这可能是代码重用的一个标志,但如果方法调用过于频繁,也可能增加理解和维护的难度。 - 类和接口数量:
项目中有4个类和接口,这个数量相对较少,可能意味着代码的模块化程度不高,或者某些类承担了过多的职责。 - 最复杂方法:
Main.main()方法是最复杂的,这通常不是一个好的实践,因为main方法应该是程序的入口点,应该尽可能简单,调用其他方法来执行具体的任务。
平均每个方法的语句数量:
平均每个方法的语句数量为5.25,这个数字相对较低,表明方法可能相对简单。
2.3答题判题程序-3设计与分析
2.3.1.答题判题程序-3的PowerDesigner的uml类图
🎨🎨🎨

类和属性说明
- 类名
Question:
属性:
int number:题目编号。
String content:题目内容。
String standardAnswer:标准答案。
方法:
Question(int number, String content, String standardAnswer):构造函数。
int getNumber():返回题目编号。
String getContent():返回处理后的题目内容。
String getStandardAnswer():返回标准答案。
boolean checkAnswer(String answer):检查给定答案是否正确。 - 类名
TestPaper:
属性:
int number:试卷编号。
ListquestionOrder:存储问题编号的顺序。
Map<Integer, Question> questions:存储问题编号和题目对象的映射。
Map<Integer, Integer> scores:存储问题编号和分数的映射。
int maxScore:试卷总分。
SetdeletedQuestions:存储已删除的问题编号。
方法:
TestPaper(int number):构造函数。
void addQuestion(int questionNumber, Question question):添加题目。
void addScore(int questionNumber, int score):添加题目分数。
String getQuestionContent(int questionNumber):获取题目内容。
boolean isTotalScoreCorrect():检查总分是否正确。
ListgetAlerts():获取警告信息。
ListgetQuestionNumbers():获取所有题目编号。
int getScore(int questionNumber):获取题目分数。
boolean checkAnswer(int questionNumber, String answer):检查答案是否正确。
void deleteQuestion(int questionNumber):删除问题。 - 类名
Student:
属性:
String id:学生ID。
String name:学生姓名。
方法:
Student(String id, String name):构造函数。
String getId():返回学生ID。
String getName():返回学生姓名。 - 类名
AnswerSheet:
属性:
TestPaper paper:关联的试卷对象。
Map<Integer, String> answers:存储答案的映射。
Listresults:存储答案正确与否的列表。
Listscores:存储分数的列表。
String studentId:学生ID。
String studentName:学生姓名。
方法:
AnswerSheet(TestPaper paper, String studentId):构造函数。
void addAnswer(int number, String answer):添加答案。
ListgetAnsNumbers():获取答案编号列表。
void judgeAnswers():判断答案正确性并计算分数。
ListgetFormattedAnswers():获取格式化后的答案。
String getScoreResults(String studentName):获取分数结果。 - 主类
Main:
方法:
main(String[] args):主函数,程序入口。
关系说明
关联(Association):
TestPaper 包含 Question 对象,表示一个试卷包含多个题目。
AnswerSheet 使用 TestPaper 对象,表示一个答卷对应一个试卷。
AnswerSheet 引用 Student 对象,表示答卷与学生的关系。
依赖(Dependency):
Main 类依赖 Question、TestPaper、Student 和 AnswerSheet 类,表示主程序中创建和操作这些对象。
聚合(Aggregation):
TestPaper 聚合 Question 对象,表示试卷由多个题目组成,但题目也可以独立于试卷存在。
2.3.2答题判题程序-3的SourceMonitor的生成报表内容



具体参数分析
项目信息:
Project Directory(项目目录):C:LanQiaoTestprojectTast_Javal。
Project Name(项目名称):blog3。
Checkpoint Name(检查点名称):Baseline。
文件信息:
File Name(文件名):Main.java。
Lines(代码行数):340。
Statements(语句数量):236。
Percent Branch Statements(分支语句占比):21.6%。
Method Call Statements(方法调用语句数量):168。
Percent Lines with Comments(带注释的行数占比):5.9%。
Classes and Interfaces(类和接口数量):5。
Methods per Class(平均每个类的方法数量):5.00。
Average Statements per Method(平均每个方法的语句数量):7.64。
复杂度相关:
Name of Most Complex Method(最复杂方法名称):Main.main ()。
Maximum Complexity(最大复杂度):26。
Line Number of Deepest Block(最深代码块所在行号):255。
Maximum Block Depth(最大代码块深度):6。
Average Block Depth(平均代码块深度):2.31。
复杂方法详情:
Most Complex Methods in 5 Class (es)(5 个类中的最复杂方法)列出了多个方法的复杂度、语句数量、最大深度和调用次数,如 AnswerSheet.addAnswer ()(3,4,2,3)、AnswerSheet.AnswerSheet ()(1,4,2,2)、AnswerSheet.getAnsNumbers ()(1,1,2,2)等。
- 由以上数据可知,我的第三题代码优劣:
- 注释覆盖率:
带注释的行数占比为5.9%,这个比例相对较低。一般来说,良好的代码应该有足够的注释来帮助理解代码的意图和逻辑,特别是在复杂或重要的部分。
代码行数与方法数量:
文件中有340行代码,平均每个类有5个方法,这意味着每个方法大约有68行代码。这个数字可能表明方法可能过于庞大,难以维护。通常,较短的方法更容易理解和维护。
代码复杂度:
最复杂方法的复杂度为26,这是一个相对较高的数值。高复杂度通常意味着方法执行了多个不同的任务,这可能导致维护困难和潜在的错误。
最复杂方法的最大代码块深度为6,平均代码块深度为2.31。这表明虽然最复杂方法的嵌套结构较深,但整体平均深度较低,这可能是一个积极的信号。 - 分支语句和方法调用:
分支语句占比为21.6%,这个比例适中,表明代码中有一定的逻辑分支,但不至于过于复杂。
方法调用语句数量为168,这表明代码中有很多方法之间的交互,这可能是代码重用的一个标志,但如果方法调用过于频繁,也可能增加理解和维护的难度。 - 类和接口数量:
项目中有5个类和接口,这个数量相对较少,可能意味着代码的模块化程度不高,或者某些类承担了过多的职责。
最复杂方法:
Main.main()方法是最复杂的,这通常不是一个好的实践,因为main方法应该是程序的入口点,应该尽可能简单,调用其他方法来执行具体的任务。
3. 采坑心得

🤗🤗🤗
- 至此,感谢大家看完了我在此之前规规整整的理论分析,在条条框框列出我踩过的坑之前,我要先说我的部分心得。踩坑真的会让人心心念念难舍难忘,我最大的感受就是鸭梨山大了,坑摔我千百遍,我却待坑如初恋😭,不论我如何焦灼,我都要一边改一边测试的时候双手合十默念通过,球球🥺。
- 在一路踩坑的历程中,是坑打开了我深夜凌晨两点定时变成emo一姐的开关,是坑让我体会到了挤牙膏式过测试点的苦涩难以下咽,是坑让我感受到全网搜遍解决办法全都试用过测试点不过反退的绝望。
- 说实话,踩完三个题目集,我不知道我学会了多少东西,平均下来每个题目集大概也是十六个小时,没错,这让我很讨厌代码天赋狗😭,我每天磨了那么久,但在天赋面前,我就是在花成两三倍的时间,脑子没干货的情况下去缝缝补补每一个测试点。题目一代码量不明显,但是题目二到题目三,在题目难度加大的情况下,我把代码量缩减了一百行,这叫程序优化吗,不,我只知道我正一步步从水货里翻出能起到关键作用的干货😑。
- 我希望我的这些坑,都不白踩,也希望如果大家遇到了相似的问题,我的解决方法真的能够帮到你🤗。我会这样想,有部分原因是,在每一次我感觉到我已经极限了无法再更进一步的时候,只要我去和大家相互沟通询问排查问题,总还是能开辟新道路新方法,直到每一个题目集都满分。我受益于分享,也希望我的分享能让大家成为收益人😇。
3.1答题判题程序-1采坑心得

public String getContent() { return content; }
这无法实现题目要求的去除题目内容末尾空格以及如果开头有空格则去除开头空格的功能,当题目内容的首尾含有空格时,输出未经空格处理的题目内容,导致提交代码测试用例答案错误。

改进后的代码新增了去除题目内容末尾空格以及如果开头有空格则去除开头空格的功能,实现了对题目内容的更精确提取和整理。
点击查看代码
//题目内容访问器
public String getContent() {
int endIndex = content.length();
for (int i = content.length() - 1; i >= 0; i--) {
if (content.charAt(i)!= ' ') {
endIndex = i + 1;
break;
}
}
String result = content.substring(0, endIndex);
char str=result.charAt(0);
if(str==' ')
return result.substring(1);
else
return result;
}

因此,如果截取的子字符串两端存在空白字符,这些字符将被包含在内,可能导致Integer.parseInt() 抛出 NumberFormatException,因为空格不能被解析为整数。
int number = Integer.parseInt(questionLine.substring(questionLine.indexOf(":") + 1, questionLine.indexOf(" #Q:")));//题目编号

在改进后的代码中,通过调用 trim() 方法,去除了截取的子字符串两端的空白字符。这样可以确保传递给 Integer.parseInt() 的字符串不包含任何前导或尾随空白字符,从而避免了潜在的 NumberFormatException。
int number = Integer.parseInt(questionLine.substring(questionLine.indexOf(":") + 1, questionLine.indexOf(" #Q:")).trim());//题目编号

除去一些符合错误或者方法调用错误,以下是该代码我的最大一个坑,主要是考虑部分题目未被回答这个情况,也是属于本题测试点之一,其实在改进过程中,不是一下子得到最终代码的,但具体细化到每次错误里,就是条件语句的逻辑优化问题。
原代码和改进后的代码在功能上的主要区别在于处理答案列表和判断结果列表的方式。改进后的代码增加了对列表大小的检查,并且优化了答案和判断结果的添加过程。
- 新增功能和转化:
初始化判断结果为默认值:在改进后的代码中,judgmentOut 列表被初始化为包含 "false" 的列表,这为每个问题提供了一个默认的判断结果。
边界检查:改进后的代码在添加答案和判断结果到列表时,增加了对列表大小的检查,确保不会发生 IndexOutOfBoundsException。
优化结果更新:改进后的代码使用 set() 方法更新 judgmentOut 列表中的值,而不是在每次迭代时添加新的值。
在原代码中,如果 answerSheet.answerslist 或answerSheet.JudgeResults 的大小小于 questionCount,将发生 IndexOutOfBoundsException,因为代码没有检查列表的大小。
点击查看代码
for (int i = 0; i < questionCount; i++) {
answerSheet.judge(i);
answerOut.add(paper.questions.get(i).getContent() + "~" + answerSheet.answerslist.get(i));
judgmentOut.add(answerSheet.JudgeResults.get(i)? "true" : "false");
}

在改进后的代码中,首先为 judgmentOut 列表中的每个问题设置默认值 "false"。然后,只有在所有列表的大小都大于或等于当前索引 i 时,才更新 judgmentOut 列表和 answerOut 列表中的值。这确保了即使某些问题没有答案或判断结果,代码也不会抛出异常,而是在未答题的情况下输出答案错误。最后,使用 set() 方法更新 judgmentOut 列表中的值,而不是添加新的值。
点击查看代码
for(int i=0;i<questionCount;i++) {
judgmentOut.add("false");
}
for (int i = 0; i < questionCount && i < paper.questions.size() && i < answerSheet.answerslist.size() && i < answerSheet.JudgeResults.size(); i++) {
answerSheet.judge(i);
judgmentOut.set(i, answerSheet.JudgeResults.get(i)? "true" : "false");
}
for(int i=0;i<questionCount;i++) {
if(i < paper.questions.size() && i < answerSheet.answerslist.size() && i < answerSheet.JudgeResults.size())
answerOut.add(paper.questions.get(i).getContent() + "~" + answerSheet.answerslist.get(i));
else
answerOut.add(paper.questions.get(i).getContent() + "~");
}

走到这里,答题判题程序第一题的编程部分终于完结撒花了😛
3.2答题判题程序-2采坑心得

题目要求输出格式"alert: full score of test paper"+试卷号+" is not 100 points",即试卷paper紧跟试卷号没有空格间隔。
原代码
alerts.add("alert: full score of test paper " + number + " is not 100 points");
使得格式错误

改正后代码
alerts.add("alert: full score of test paper" + number + " is not 100 points");

这个小坑没有什么需要深度学习思考的,但是提供了注意格式检查这一过测试点思路。
举例说明:
假设输入行是#T:1 2-10 3-20,其中包含试卷编号(1),两个分数(2-10和3-20)。在原始代码中,如果直接访问parts[2]而不考虑parts数组的长度,当parts数组长度小于3时,将抛出异常。而在改进后的代码中,通过检查parts数组的长度,确保了只有当确实存在第三个部分时,才会尝试处理它,从而避免了异常的发生。
原代码时,如果输入行不包含至少两个或三个部分,直接访问parts[1]、parts[2]、parts[3]将导致程序抛出异常。
点击查看代码
String[] parts = line.substring(3).split(" ");
for (String score : parts[1].split("\\s+")) {}
for (String score : parts[2].split("\\s+")) {}
for (String score : parts[3].split("\\s+")) {}

改进后的代码
例如 if(parts.length>=3) { for (String score : parts[2].split("\\s+")) {}}
在处理每一行输入时,代码首先检查该行是否包含足够的部分(由空格分隔)。这是通过检查parts数组的长度来实现的。
通过if(parts.length>=2)、if(parts.length>=3)和if(parts.length>=4)这些条件语句,代码确保在尝试访问parts[1]、parts[2]和parts[3]之前,这些元素确实存在。这避免了在输入行不包含足够数据时尝试访问不存在的数组索引,从而防止了ArrayIndexOutOfBoundsException。

在题目要求下,试卷总分警示要最先提示,其次是答卷试卷号信息是否报错,只有试卷号信息正确的情况下,才能够进行答卷信息判分信息的输出
原代码没有找到对应的试卷,循环结束后不会输出试卷总分警示
点击查看代码
else if (line.startsWith("#S:")) {
TestPaper testPaper = testPapers.get(testNumber);
if (testPaper == null) {
System.out.println("The test paper number does not exist");
continue;
}
}
// 输出试卷总分警示
//...
// 输出答卷信息和判分信息
//...

改进后的代码在开始部分声明了一个名为 testnull 的 TestPaper 类型的变量,这个变量用于存储从 testPapers 映射中检索到的试卷对象。如果 testPaper 为 null,即没有找到对应的试卷,使用 continue 语句跳过当前迭代,进入下一个循环。即使没有找到对应的试卷,循环结束后也会输出试卷总分警示,而在原代码中,这部分逻辑是在一起的。
点击查看代码
TestPaper testnull = null;
else if (line.startsWith("#S:")) {
//...
testnull=testPaper;
if (testPaper == null) {
continue;
}
}
// 输出试卷总分警示
//...
if (testnull == null) {
System.out.println("The test paper number does not exist");
}
else {
// 输出答卷信息和判分信息
}

在答案AnswerSheet类的addAnswer方法中,改进后的代码添加了逻辑来确保answers列表至少与paper中的题目数量一样长。如果answers列表的长度小于题目数量,它会添加null值直到列表长度足够。这确保了在评分时不会出现IndexOutOfBoundsException。
原代码的addAnswer方法非常简单,它只接受一个String类型的参数answer,并将其添加到answers列表中。这种方法没有考虑答案的顺序或与特定问题的关联,也没有处理答案列表大小不足的情况。
public void addAnswer(String answer) { answers.add(answer); }

改进后的addAnswer方法增加了以下功能:
参数变更:
方法现在接受两个参数:int number和String answer。number参数用于指定答案对应的问题编号,这使得每个答案都可以与特定的问题关联起来。
动态扩展答案列表:
在添加答案之前,方法会检查answers列表的大小是否小于或等于试卷中问题的数量。如果是,它会通过添加null值动态扩展answers列表,直到其大小足以容纳所有问题的答案。这确保了在添加答案时不会出现IndexOutOfBoundsException。
条件性地设置答案:
如果传入的answer不为null,则将该答案设置在answers列表的指定number位置。如果answer为null,则在该位置设置null。这允许方法处理空答案的情况。
点击查看代码
public void addAnswer(int number,String answer) {
while(answers.size()<=paper.getQuestionNumbers().size()) {
answers.add(null);
}
if(answer!=null)
answers.set(number,answer);
else
answers.set(number,null);
}

3.3答题判题程序-3采坑心得

- 最后一题写了很久,约莫有二十小时了,说我踩坑呢其实归根结底还是几个逻辑问题的细节没处理好🤐
- 异常格式正则表达式判断不完全、异常格式输出优先级、各个信息没有按照固定输出顺序,规定先总分判断->再试卷是否存在判断->答卷信息->学生信息是否存在->答卷分数、答卷答题编号对应试卷相应位置的题号
- 每个bug都是试了很多种逻辑表达去解决,但还是直接从0到1的进行总结,因为中间过程的错误思路不值得学习和应用。
答卷答题编号实际对应试卷该索引位置的题号(#T:1 2-5 3-8,#S:1 ... #A:2-4 #A:1-5,其中答卷2-4对应试卷3-8,答卷1-5对应试卷2-5),而我简单将原试卷题目编号与答卷答题编号对应(答卷2-4对应试卷2-5,答卷1-5对应试卷不存在的1号题),且题目被删除时不能正确的处理答案,使得答案与题目对应错误,当试卷2-5被删掉,我的该题答案会匹配下一位置的题目,完全不对应。
原代码处理过程
在judgeAnswers方法和getFormattedAnswers方法中,我使用循环0-题目总数索引i获取答案字符串answers.get(i),会因为索引与题目编号的对应关系不清晰而导致错误。例如,如果在处理过程中题目编号的顺序发生变化,会导致获取错误对应的答案。
点击查看代码
public void judgeAnswers() {
List<Integer> questionNumbers = paper.getQuestionNumbers();
for (int i = 0; i < questionNumbers.size(); i++) {
int questionNumber = questionNumbers.get(i);
String content = paper.getQuestionContent(questionNumber);
String answerStr = answers.get(i);
//...
}
在新增答案内容时,原代码使用 List
List<String> answers;
点击查看代码
for (int i = 0; i < answers.length; i++) {
String[] ans=answers[i].trim().split("-");
answerSheet.addAnswer(i, ans[1]);//
}

改进后代码处理过程
在judgeAnswers方法和getFormattedAnswers方法中,使用题目编号answers.get(questionNumber)直接获取答案字符串,更加直观地对应题目编号和答案。在处理这个用例时,能够准确地获取每个题目的答案,并根据题目是否被删除进行正确的处理。
点击查看代码
public List<String> getFormattedAnswers() {
List<Integer> questionNumbers = paper.getQuestionNumbers();
for (int i = 0; i < questionNumbers.size(); i++) {
int questionNumber = questionNumbers.get(i);
String answer = answers.get(questionNumber); // 使用题目编号来获取答案
String content = paper.getQuestionContent(questionNumber);
}}
在处理答卷信息部分,处理答案的逻辑更加清晰。首先分割输入字符串获取答案部分,然后遍历答案部分,分割每个答案字符串获取题目顺序索引和答案内容。如果题目顺序索引合法,则获取实际的题目编号,并将答案添加到答卷中。在处理这个用例时,能够准确地获取答案内容,并将其添加到正确的题目编号对应的答案列表中。
点击查看代码
String[] answerParts = line.split("#A:");
for (int i = 1; i < answerParts.length; i++) {
String[] ans = answerParts[i].split("-");
// 检查分数是否为数字
if (ans.length == 2) {
int answerIndex = Integer.parseInt(ans[0].trim()) - 1; // 这是题目的顺序索引
if (answerIndex < testPaper.getQuestionNumbers().size()) {
int questionNumber = testPaper.getQuestionNumbers().get(answerIndex); // 获取实际的题目编号
String answer = ans[1].trim();
if(answer!=null)
answerSheet.addAnswer(questionNumber, answer); // 添加答案
}
}
}

原代码
点击查看代码
if (line.startsWith("#N:")) {
if (parts.length < 3 || !line.contains(" #Q:") || !line.contains(" #A:")) {
System.out.println("wrong format:" + line);
continue;
}
}
原代码通过检查 parts 数组的长度和字符串中是否包含 #Q: 和 #A: 标签来验证格式。这种方法虽然可以检查基本的格式问题,但它不够灵活和强大,因为它不能确保问题编号是数字,也不能确保问题内容和答案内容之间有正确的空格分隔。
无法正确判断格式错误并给出提示:

改进后代码
点击查看代码
if (line.startsWith("#N:")) {
String shunxu="#N:\\d+\\s#Q:.+\\s#A:.+";//匹配问题
if (!line.matches(shunxu)) {
System.out.println("wrong format:" + line);
continue;
}
}

原代码
点击查看代码
if (paper.deletedQuestions.contains(questionNumber)) {
if (answer == null) {
formatted.add("answer is null");
} else {
formatted.add("the question " + questionNumber + " invalid" + "~0");
}
}
else if (content == null) {
formatted.add("non-existent question~0");
}
else {
formatted.add(content + "~" + answer + "~" + (isCorrect ? "true" : "false"));
}
原代码没有明确处理问题不存在的情况。在处理被删除的问题时,无论答案是否为空,都会添加相同的消息。这可能导致在答案不为空时,错误地标记问题为无效。且对答案为空和问题不存在的处理分散在多个if-else语句中,这使得代码难以阅读和维护。
异常答卷没有正确提示

改进后的代码
点击查看代码
if(answers.containsKey(questionNumber)) {
String answer = answers.get(questionNumber); // 使用题目编号来获取答案
boolean isCorrect = results.get(i);
if (content == null) {
formatted.add("non-existent question~0");
}
else {
if (paper.deletedQuestions.contains(questionNumber)) {
formatted.add("the question " + questionNumber + " invalid" + "~0");
}
else {
formatted.add(content + "~" + answer + "~" + (isCorrect ? "true" : "false"));
}
}
}
else {
formatted.add("answer is null");
}

这个bug我愣是找了两个小时左右,离谱的很,一连就是三个测试点,原理也非常好理解。在我的原代码中,我使用了line.startsWith(""),试图每类信息都对应自己的处理方式,题目中已给的开头有题目类#N:、试卷类#T:、学生类#X:、答卷类#S:、删除类#D:,所以我只考虑了这些类开头。
点击查看代码
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty() || line.equals("end")) {
break; }
if (line.startsWith("#N:")) {}
else if (line.startsWith("#T:")) {}
else if (line.startsWith("#X:")) {}
else if (line.startsWith("#S:")) {}
else if (line.startsWith("#D:")) {}

改进后代码,将开头除此之外的所有可能的异常输入都归类为wrong format
else { System.out.println("wrong format:" + line); continue; }

4. 改进建议
- 以下内容是我对自己每个题目代码的优化改进想法🤗:
4.1答题判题程序-1改进建议
-
优化字符串处理逻辑
改进点:
Question 类中的 getContent() 方法可以通过正则表达式简化,以提高代码的可读性。
public String getContent() { return content.trim().replaceAll("^\\s+|\\s+$", ""); // 去除前后空白字符 } -
优化列表初始化
改进点:
Paper 类中的 SaveQuestion() 方法使用循环来确保列表有足够的大小,这可能影响性能。
可以改为
点击查看代码
public Paper(int questionCount) {
this.questionCount = questionCount;
questions = new ArrayList<>(Collections.nCopies(questionCount, null)); // 初始化时就分配足够的空间
}
public void SaveQuestion(int number, Question question) { questions.set(number, question); }
-
错误处理和输入验证
改进点:
代码中对输入的验证不够充分,例如没有检查用户输入的题目编号是否有效。
建议在 Paper 类中添加对题目编号的验证,确保它们在合理的范围内。
对用户输入的答案进行验证,确保它们不为空,并且格式正确。(因为在题目三中增加了此项功能,所以不赘述) -
引入单元测试
改进点:
代码中没有包含单元测试。
比如为 Question 类编写单元测试:
点击查看代码
public class QuestionTest {
@Test
public void testCheckOneAnswer() {
Question question = new Question(1, "What is 2+2?", "4");
assertTrue(question.checkOneAnswer("4"));
assertFalse(question.checkOneAnswer("5"));
assertFalse(question.checkOneAnswer(null));
}
}
4.2答题判题程序-2改进建议
- 使用更具描述性的命名
将 content 属性重命名为 questionContent,以更清楚地表明这是问题的内容。
将 standardAnswer 属性重命名为 correctAnswer,以更直观地表示正确答案。
2.异常处理
在 TestPaper 类中添加异常处理,例如当尝试获取不存在的题目内容或分数时,应抛出异常或返回一个错误消息,而不是返回 null。
点击查看代码
public String getQuestionContent(int questionNumber) {
Question question = questions.get(questionNumber);
if (question == null) {
throw new IllegalArgumentException("Question not found");
}
return question.getContent();
}
3.使用Java 8特性
尝试使用 Optional 类来避免 NullPointerException,并使代码更易读。
点击查看代码
public String getQuestionContent(int questionNumber) {
return questions.get(questionNumber)
.map(Question::getContent)
.orElseThrow(() -> new IllegalArgumentException("Question not found"));
}
4.单一职责原则
TestPaper 类承担了太多职责,包括管理问题、分数和检查答案。考虑将分数管理分离到另一个类中,例如 ScoreManager。
5.使用Builder模式
对于创建复杂对象(如 TestPaper)时,使用建造者模式(Builder Pattern)可以使代码更清晰。
例如
点击查看代码
public static class TestPaperBuilder {
private int number;
private Map<Integer, Question> questions = new LinkedHashMap<>();
private Map<Integer, Integer> scores = new HashMap<>();
public TestPaperBuilder setNumber(int number) {
this.number = number;
return this;
}
//...
}
6.单元测试
为每个类编写单元测试,确保代码的正确性和后续的可维护性。
例如
点击查看代码
@Test
public void testQuestionContent() {
Question question = new Question(1, "What is 2 + 2?", "4");
assertEquals("What is 2 + 2?", question.getContent());
}
7.日志记录
添加日志记录,以便于调试和跟踪问题。
4.3答题判题程序-3改进建议
-
使用更具描述性的变量名
将 shunxu、test、delete_ 等变量名替换为更具描述性的名称,以提高代码的可读性。
例如
String questionFormat = "#N:\\d+\\s#Q:.+\\s#A:.+"; -
优化正则表达式匹配
将多个独立的正则表达式匹配合并为一个更全面的正则表达式,以减少代码重复。 -
异常处理
在解析输入行时,添加异常处理逻辑,以捕获并处理可能出现的 NumberFormatException 或 IndexOutOfBoundsException。 -
使用Java 8的Optional类
使用 Optional 类来避免在获取映射值时出现 NullPointerException。
例如
点击查看代码
Question question = questions.get(questionNumber);
if (question != null) {
return question.getContent();
} else {
return null;
}
改进后的代码:
return questions.getOrDefault(questionNumber, new Question(-1, "Unknown", "")).getContent();
- 代码重构
将重复的代码块提取为单独的方法,以提高代码的可维护性。
例如
原代码:
点击查看代码
String[] parts = line.substring(3).split(" ");
int testNumber = Integer.parseInt(parts[0].trim());
改进后的代码:
点击查看代码
private int parseTestNumber(String line) {
String[] parts = line.substring(3).split(" ");
return Integer.parseInt(parts[0].trim());
}
// 在 main 方法中使用
int testNumber = parseTestNumber(line);
-
增加日志记录
添加日志记录,以便于调试和跟踪问题。
logger.error("Wrong format: {}", line); -
使用Java标准库
使用Java标准库中的类和方法,如 Arrays.asList() 和 Collections.sort(),以简化代码。
8.支持多种输入顺序
目前代码假设输入信息的顺序有一定的限制,如题目信息必须在试卷信息之前输入。可以改进代码以支持更灵活的输入顺序。
例如,可以在处理试卷信息时,如果引用了一个尚未输入的题目编号,可以先记录下来,等到该题目编号的题目信息输入时再进行处理。
5. 总结
5.1本阶段三次题目集收获

一、面向对象编程的深入理解
(一)类的设计与封装
在这三个代码中,通过定义不同的类,如Question、Paper/TestPaper、AnswerTest/AnswerSheet和Student等,将相关的数据和操作封装在一起。
例如,Question类封装了题目编号、内容和标准答案,以及检查答案的方法。这样可以使代码更加清晰,便于维护和扩展。外部代码不需要了解Question类的内部实现细节,只需要通过调用公开的方法来获取题目信息或检查答案。
封装性有助于提高代码的可维护性和可扩展性。如果需要修改题目类的实现,只需要在Question类内部进行修改,而不会影响到其他类的代码。同时,如果需要添加新的功能,也可以在相应的类中进行扩展,而不会破坏现有代码的结构。
(二)类之间的关系
代码中展示了不同类之间的多种关系,如关联、组合等。
例如,Paper/TestPaper类与Question类之间是组合关系,一个试卷对象包含多个题目对象。AnswerSheet类与Paper/TestPaper类之间是关联关系,一个答卷对象依赖于一个试卷对象。
Student类与AnswerSheet类之间也存在关联关系,通过学生 ID 可以在AnswerSheet中获取对应的学生信息。
理解类之间的关系有助于设计更加合理的软件架构。通过明确类之间的关系,可以更好地组织代码,提高代码的可读性和可维护性。同时,合理的类关系设计也可以提高代码的可扩展性,方便在未来添加新的功能或修改现有功能。
二、数据结构的选择与应用
(一)列表(List)的灵活运用
在第一个代码中,Paper类使用List
在处理答案和判题结果时,AnswerTest类使用List
列表的优点是可以方便地进行遍历和访问元素,适用于需要顺序处理数据的场景。在代码中,通过遍历列表,可以对每个题目进行处理,输出答案和判题结果。
(二)映射(Map)的高效查找
在第二和第三个代码中,广泛使用了映射(Map)数据结构。例如,TestPaper类中的Map<Integer, Question>用于存储题目编号与题目对象的映射,Map<Integer, Integer>用于存储题目编号与分数的映射。
AnswerSheet类中的Map<Integer, String>用于存储题目编号与答案的映射。通过映射,可以快速地根据题目编号获取对应的题目、分数或答案,提高了代码的执行效率。
映射的优点是可以通过键快速查找值,适用于需要根据特定键进行查找的场景。在代码中,通过映射可以方便地获取题目信息、分数和答案,进行判题和结果输出。
三、输入处理与格式验证
(一)从标准输入读取数据
三个代码都使用了Scanner从标准输入读取用户输入的数据。这种方式可以方便地获取用户输入的文本信息,并进行进一步的处理。
通过不断读取用户输入的行,直到遇到特定的结束标志(如 “end”),可以实现灵活的输入处理。这种方式适用于需要用户交互的程序,可以根据用户的输入动态地进行处理。
从标准输入读取数据的方式简单直观,易于实现。同时,通过Scanner的方法,可以方便地对输入进行分割和解析,获取需要的信息。
(二)格式验证的重要性
在第三个代码中,对输入的格式进行了较为严格的验证。例如,使用正则表达式来验证题目、试卷、学生和答卷等信息的格式,确保输入的数据符合预期的格式要求。
如果输入格式不正确,会输出错误信息并继续等待正确的输入。这种方式可以提高程序的稳定性和可靠性,防止因输入错误的数据而导致程序出现异常或错误的结果。
格式验证是保证程序正确性的重要手段。通过对输入进行格式验证,可以确保程序接收到的是合法的数据,避免因数据格式错误而导致的程序错误。同时,输出错误信息可以帮助用户快速定位问题并进行修正,提高用户体验。
四、错误处理与异常处理
(一)错误信息输出
当输入格式错误或出现其他错误情况时,三个代码都输出了相应的错误信息,以便用户了解问题所在。
例如,如果输入的题目格式不正确,会输出 “wrong format:” 加上具体的错误信息。如果试卷编号不存在,会输出 “The test paper number does not exist”。
输出错误信息可以帮助用户快速定位问题并进行修正,提高用户体验。同时,错误信息的输出也有助于开发人员进行调试和维护,快速发现和解决问题。
(二)异常处理的潜在需求
虽然这三个代码中没有显式的异常处理,但可以考虑在可能出现异常的地方添加适当的异常处理代码。
例如,在将字符串转换为整数时,可能会出现NumberFormatException异常。可以使用try-catch块来捕获并处理这种异常,避免程序因异常而崩溃。
在从映射中获取值时,如果键不存在,可能会返回null。可以添加适当的检查,避免因null值而导致的程序错误。
合理的异常处理可以使程序更加健壮,能够更好地应对各种异常情况。通过捕获和处理异常,可以避免程序因异常而崩溃,提高程序的稳定性和可靠性。同时,异常处理也可以提供更加友好的错误信息,帮助用户快速定位问题并进行修正。
五、代码结构与可读性
(一)方法和类的划分
三个代码都将不同的功能划分到不同的方法和类中,提高了代码的可读性和可维护性。
例如,将题目、试卷、答卷和学生等功能分别封装在不同的类中,每个类负责特定的任务。在主类中,通过调用这些类的方法来实现整个程序的功能。
方法的划分也很清晰,每个方法都完成一个特定的任务,如读取输入、处理题目、处理试卷、处理答卷等。这种方式可以使代码更加易于理解和修改,也方便其他开发者阅读和维护代码。
(二)注释和命名规范
虽然代码中可能缺少详细的注释,但良好的命名规范可以在一定程度上提高代码的可读性。
例如,类名、方法名和变量名都应该具有描述性,能够清晰地表达其用途。在这三个代码中,类名和方法名的命名较为合理,能够大致反映其功能。
变量名的命名也应该具有一定的描述性,避免使用单个字母或无意义的名称。例如,在第一个代码中,questionCount表示题目数量,paper表示试卷对象,answerSheet表示答卷对象,这些名称都能够清晰地表达其用途。
添加适当的注释可以进一步提高代码的可读性,特别是对于复杂的算法或逻辑部分,应该添加详细的注释说明其工作原理。注释可以帮助其他开发者更好地理解代码,提高代码的可维护性。同时,良好的命名规范也可以使代码更加易于理解和修改,提高代码的可读性和可维护性。
5.2深入学习探究的思考

一、面向对象设计
(一)继承与多态的应用
目前代码中没有明显的继承和多态的应用。可以考虑在适当的地方使用继承和多态来提高代码的可扩展性和可维护性。
例如,可以创建一个抽象的QuestionBase类,然后让不同类型的题目(如选择题、填空题等)继承自这个抽象类,实现各自特定的方法。
多态可以使代码更加灵活,例如,可以定义一个通用的接口或抽象方法,让不同的类实现这个接口或方法,然后在代码中可以使用多态来调用这些方法,而不需要关心具体的实现类。
(二)设计模式的应用
代码中可以考虑应用一些常见的设计模式,以提高代码的质量和可维护性。
例如,可以使用工厂模式来创建不同类型的题目对象或试卷对象,使代码更加灵活和可扩展。
观察者模式可以用于实现当答案发生变化时,自动更新判题结果和得分信息。
学习和应用设计模式可以使代码更加优雅、高效和可维护。同时,设计模式也可以帮助开发者更好地组织代码,提高代码的可读性和可扩展性。
二、数据结构的选择与优化
(一)数据结构的性能分析
在代码中,使用了不同的数据结构,如List、Map和Set。可以对这些数据结构的性能进行分析,以确定是否选择了最合适的数据结构。
例如,对于频繁插入和删除元素的情况,LinkedList可能比ArrayList更合适。对于需要快速查找的情况,HashMap可能比LinkedHashMap性能更好。
可以使用性能测试工具来对不同的数据结构进行性能测试,以确定在特定的场景下哪种数据结构最适合。
(二)数据结构的优化
对于一些数据结构的使用,可以进行优化以提高性能。例如,在AnswerSheet类中,使用Map<Integer, String>来存储答案,可以考虑使用其他数据结构来减少内存占用或提高查找速度。
可以根据具体的需求对数据结构进行定制化的优化,例如,对于特定的问题,可以实现自己的数据结构来满足特定的性能要求。
5.3问题改进与建议

- 对教师、课程、作业、实验、课上及课下组织方式等方面的改进建议及意见:
从本学期我的学习情况来看,PTA题目集给我的压力还是不小的,虽然我能通过时间换成绩的方式完成整体代码的编写,但是题目集截止的时间实在是太快啦,很希望能多给点时间😭。
也许deadline真的能促使我投入不少时间精力,但我甚至不曾系统认真的学习过Java语法,总是测试点需要什么,我就全网搜索能够作为支撑的方法案例再举一反三应用到我的代码中,这样做的漏洞很多,如果可以隔段时间进行整体代码练习、一段时间进行知识点学习考察,我想我应该能学到更多测试点以外的知识🤕。
现在这样写出来的满分给我的感受就是有点镜中花水中月,我摸不清我是会自主学习自主运用代码了,还是仅仅会看懂别人的案例再应用到自己的程序,心里没底的很,这可能也和我每次花大量时间编译好完整程序,但是没时间整理程序所含的知识点有关,但我还是希望成年人的压力不要这样大🥺。
读到这里,已经是阶段性收尾了,写完了三次题目集还有本次Blog总结,我都要由衷的说,我们真的辛苦了,能做到尽力尽量已经很棒了🤗。每次我都深感天赋者给的压力,所以能做出自己的成绩,真的都很棒🥳。
6. 答题判题程序-3代码节选分析、顺序图
- 首先,我截取了涉及到#N:题目信息处理部分的代码
点击查看代码
class Question {
int number;
String content;
String standardAnswer;
public Question(int number, String content, String standardAnswer) {
this.number = number;
this.content = content;
this.standardAnswer = standardAnswer;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Map<Integer, Question> allQuestions = new HashMap<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty() || line.equals("end")) {
break;
}
if (line.startsWith("#N:")) {
String shunxu="#N:\\d+\\s#Q:.+\\s#A:.+";//匹配问题
if (!line.matches(shunxu)) {
System.out.println("wrong format:" + line);
continue;
}
int number = Integer.parseInt(line.substring(line.indexOf(":") + 1, line.indexOf(" #Q:")).trim());//题目编号
String content = line.substring(line.indexOf(" #Q:") + 4, line.indexOf(" #A:"));//题目内容
String standardAnswer = line.substring(line.indexOf(" #A:") + 4).trim();//题目标准答案
Question question = new Question(number, content, standardAnswer);
allQuestions.put(number, question);//加入题目信息
}
}
}
}
- 该段代码整体功能概述
读取用户输入的问题描述行,提取问题编号、内容和标准答案,并将这些信息存储在一个哈希映射(HashMap)中。如果输入格式不正确,则输出错误提示信息。 - 代码内容详细分析
Question类
这个类用于表示一个问题,包含问题编号、内容和标准答案三个属性。
构造方法public Question(int number, String content, String standardAnswer)用于初始化问题对象。
getNumber()方法返回问题的编号。
getContent()方法:
通过循环从后往前找到问题内容字符串中最后一个非空格字符的位置,然后截取从字符串开头到这个位置的子串作为结果。
接着,检查结果字符串的第一个字符是否为空格,如果是,则返回去掉第一个字符后的子串;否则,直接返回这个子串。
getStandardAnswer()方法返回问题的标准答案。
checkAnswer(String answer)方法:如果传入的答案为null,则返回false;否则,比较传入的答案与问题的标准答案是否相等,相等则返回true,否则返回false。
Main类
main方法是程序的入口点。
创建了一个Scanner对象用于读取用户输入。
创建了一个Map<Integer, Question>类型的哈希映射allQuestions,用于存储问题编号和对应的问题对象。
使用while (scanner.hasNextLine())循环不断读取用户输入的行,直到没有更多输入或输入为end。
对于每一行输入:
首先调用trim()方法去除行两端的空白字符。
如果输入为空或等于end,则跳出循环。
如果输入以#N:开头,则进行问题信息提取:
定义正则表达式String shunxu="#N:\d+\s#Q:.+\s#A:.+";用于匹配问题格式,即#N:后面跟着一个数字、一个空格、#Q:后面跟着问题内容、一个空格、#A:后面跟着标准答案。
如果输入不匹配这个正则表达式,则输出错误提示信息System.out.println("wrong format:" + line);并继续下一次循环。
如果匹配成功,则通过字符串截取的方式提取问题编号、内容和标准答案,并创建一个Question对象,然后将其放入allQuestions映射中。 - 顺序图
![]()
在这个顺序图中:
由用户启动程序,程序进入main方法。
在main方法中,创建Scanner对象用于读取用户输入。
进入循环,等待用户输入一行字符串。
用户输入一行字符串。
程序去除字符串两端的空白字符。
判断字符串是否为空或等于"end",如果是,则结束循环;如果不是,则继续下一步。
判断字符串是否以"#N:"开头,如果是,则继续下一步;如果不是,则回到步骤3。
检查字符串是否符合正则表达式格式,如果不符合,则输出错误提示信息,回到步骤3;如果符合,则继续下一步。
提取问题编号、内容和标准答案。
创建Question对象。
将问题对象放入allQuestions映射中。
回到步骤3,等待下一次用户输入。
完成截取部分介绍
欢迎大家给我好的编程建议或者学习思路,既要又要的艰巨任务真的难以做到,希望我们的程序都一把过🐱🏍

本文是对答题判题程序三次题目集的总结性 Blog。前言部分概述了题目集的知识点、题量和难度情况。设计与分析环节重点分析题目集最后一题的提交源码,结合 SourceMontor 报表和 PowerDesigner 类图进行解释并分享心得。踩坑心得部分以数据、源码和测试结果为支撑详细总结了源码提交过程中的问题及心得。改进建议部分针对题目编码提出可持续改进的见解。最后在总结部分,综合回顾三次题目集,阐述学到的内容、需进一步学习研究的地方,并给出对教师、课程等方面的改进建议和意见。

浙公网安备 33010602011771号