前言:总结三次题目集的知识点、题量、难度等情况
答题判题程序1
1.知识点
输入输出处理:
解析输入格式,包括题目数量、题目信息和答题信息。
按行处理输入内容,确保题目顺序和编号的独立性。
字符串解析与匹配:
需要识别并提取指定格式中的题号、题目内容、标准答案和用户作答答案。
按照格式要求重组输出内容。
条件判断:
将用户的每个答题与标准答案进行比对,输出 true 或 false。
题号顺序可能不按顺序输入,但输出要求按题目编号排序,因此需要建立题号与答案的关联并正确排序。
数据结构选择:
使用适合的结构(如字典或列表)来存储题目信息和答题信息,以便有效检索和匹配。
格式化输出:
输出题目数量、题目答题信息和判题结果,格式上要严格符合示例样式。
2.题量
需要完成多个部分的实现,包括题目解析、答题匹配、格式输出和判题判断。
3.难度
多层次的输入处理:包括题目数量、题目内容和答题信息,且题号顺序不固定,增加了处理难度。
容错和边界情况处理:如不按顺序输入的题号、不同题目的数量等,处理不当易导致错误。
答题判题程序2
在这道题目中,在基础答题判题程序的基础上,新增了试卷管理和判分功能,并要求以类的形式来组织程序。这些变化主要增加了输入的复杂性、信息的组织和处理逻辑的复杂性
1.新增的知识点
类设计与关联关系:
在1的基础上,需要设计多种类来存储和处理题目信息、试卷信息和答卷信息。可以考虑设计 题目类、试卷类 和 答卷类。
类之间的关联关系增强,如 试卷类 包含多个 题目类 实例,并且答卷中题目和试卷需要准确关联匹配。
试卷和题目分值管理:
增加了题目分值的设定,并且通过试卷信息将题目和分值关联起来。这就需要在题目类或其他适当的地方存储分值信息。
在答题判分时,程序需根据判题结果计算每道题的得分,并输出总分。
顺序不定的多种输入:
输入数据的顺序不定,三类信息(题目信息、试卷信息、答题信息)可以打乱输入,需要程序自行识别、解析和存储。
题目编号可能不连续,程序需忽略缺失题号。
容错与提示功能:
针对试卷分值进行校验并提示不满100分的情况,增强了判题系统的容错能力。
当答卷中使用了不存在的试卷号时,系统需要输出对应的提示信息。
计分和输出格式的复杂性:
在判题结果的基础上,需计算每道题的得分,并生成包含得分和总分的判分信息。
格式要求更为严格,例如针对没有答案的题目显示 answer is null,并按要求输出计分信息。
2.题量
要求大幅度增加,需要设计多种类、实现多种判分机制和错误提示机制。因此这道题在题量上增加了难度。
3.难度
数据解析复杂性增加:三类输入信息顺序不定,且混合输入,增加了解析和分类存储的难度。
面向对象设计要求:需要设计多个类及其关系,体现出良好的模块化设计和职责分离。
输出格式严格:输出格式的要求进一步提升,必须考虑未答题、试卷分值检查等多种情况。
数据校验与异常处理:需要检查试卷总分、题目缺失、试卷号不存在等多种边界情况,提高了程序的容错处理难度。
答题判题程序3
相比于“答题判题程序-2”,增加了多项功能和规则,进一步提升了程序的复杂性和多样性。
1.新增知识点
学生信息和学号验证:
本题新增了学生信息输入,要求程序能管理并核对学生的学号与姓名。在答卷信息中,如学号不存在于学生列表中,还需输出相应的错误提示。这要求引入学生数据管理、学生身份校验等功能。
题目删除管理:
新增了题目删除信息和管理的要求。删除题目后,该题目应在试卷中计为0分,同时输出失效提示信息。需考虑题目删除后对相关试卷及判题输出的影响。
题目引用错误检测:
如果试卷引用了不存在的题目,需检测并输出错误提示“non-existent question~0”,且该提示的优先级最高。这涉及到题目引用的有效性验证,并要求根据优先级对多种错误输出做相应处理。
格式验证:
新增了对输入信息格式的验证,要求程序能够识别并输出不符合格式要求的输入。此处涉及异常处理和严格的格式解析规则,是对输入数据准确性控制的进一步提升。
试卷号引用错误检测:
答卷信息中的试卷号若在系统中未定义,需输出“the test paper number does not exist”。这要求程序增加对试卷编号的有效性验证。
多种错误提示的优先级管理:
如果在一道题目上同时出现多种错误(题号不存在、删除、答题缺失等),需按优先级排序输出错误提示信息。这要求程序实现错误优先级排序逻辑。
2.题量
本题包含五类信息的输入与管理(题目信息、试卷信息、学生信息、答卷信息、删除题目信息),并且要求程序能够识别各类信息混杂的情况。这使得程序需要处理更多的输入类型和数据量。
多层次数据管理:试卷与题目、学生与答卷之间存在多重引用关系,且需兼顾删除题目和引用错误的情况。程序需在数据存储、检索、校验等方面设计更合理的结构,提升了对程序逻辑复杂性的要求。
3.难度:
相比于“答题判题程序-2”,本题需使用更复杂的数据结构(如嵌套字典、对象等)来管理题目、试卷和学生信息,尤其在处理删除题目、缺失题目、错误引用等情况时,设计合理的索引和查找机制更为重要。
异常处理和输入验证:需要对各种错误(格式、引用、删除等)进行精细化处理并提供优先级提示,这对异常处理能力提出了更高要求。
逻辑优先级控制:不同错误提示有特定优先级,需在判题时进行逻辑判断和排序。这对判断结构的准确性和执行顺序的合理性提出了更高要求。
输入和输出解析的准确性要求:输入数据的格式多样且存在约束条件,需正确解析并处理,并保证输出符合特定格式。这对程序的解析和输出控制能力提出了更高的要求。
设计与分析:重点对题目的提交源码进行分析,可参考SourceMontor的生成报表内容以及PowerDesigner的相应类图,要有相应的解释和心得(做到有图有真相),本次Blog必须分析题目集1~3的最后一题
源码分析类图
1.答题判题程序1
Quiz 包含 Question:Quiz 中通过数组 questionList 存储 Question 对象,属于聚合关系。
AnswerSheet 关联 Quiz:AnswerSheet 包含对 Quiz 的引用,以便访问题目信息及判题。
每个类的设计都遵循单一职责原则。例如,Question 类专注于题目内容及答案验证,而 Quiz 负责管理题目集,AnswerSheet 则专注于用户答题信息。
类图中设计了聚合关系,以便在 Quiz 和 Question 之间的关系清晰。Quiz 持有 Question 的引用,但 Question 独立存在,不依赖于 Quiz 的生命周期。
代码具备良好的扩展性。如果需要添加新的题目类型(例如选择题、填空题),可以通过继承或实现接口来扩展 Question 类,不影响其他类的结构。
代码的复用性:AnswerSheet 类利用 Quiz 的验证功能进行判题,代码清晰简洁,并且避免重复实现答案验证逻辑。
2.答题判题程序2
Question 只负责题目信息和答案验证,TestPaper 负责题目集合的管理,AnswerSheet 负责记录和判定用户答案。这种设计让系统的每个模块都易于理解和维护。
在 TestPaper 和 Question 之间采用了聚合关系。TestPaper 包含 Question 的引用,但 Question 的存在不依赖于 TestPaper 的生命周期。这种关系有助于构建试卷的层次结构,使得 TestPaper 可以灵活管理和操作多道题目。
Main 类仅负责程序的初始化和控制流程,而不直接参与具体的数据处理,这种低耦合的设计提高了系统的灵活性。
充分利用了面向对象编程的封装性、多态性和继承性
3.答题判题程序3
Question 类是考试系统的基本单元,主要存储每道题的内容及正确答案,并提供验证答案的功能。这种设计确保了每个问题都有自包含的数据和功能,方便管理和操作
AnswerSheet 类用于存储学生的答卷数据,包括每道题的答案和总分。提供的计算分数和展示内容的功能使得这个类不仅可以管理答题过程,还能够提供批改依据。
TestPaper 类相当于试卷的管理者,通过存储题目列表和分数,实现动态增删题目和分数的功能。这种设计为教师创建、编辑试卷提供了很大的灵活性。
Main 类是系统的入口和控制中心,负责输入输出的处理,以及对数据的解析和展示。这个类主要充当管理者和协调者的角色,将答题、试卷、问题等对象组织到一起,并展示给用户。这种设计可以简化用户的操作,使得系统更加易于使用。
代码分析:
Main 类包含了主方法和各种解析函数,用于处理输入数据和生成题库、试卷和答卷的结构。
输入解析:程序通过循环读取输入并识别不同的命令(如 #N: 表示题目定义,#T: 表示试卷定义等),根据命令调用对应的解析方法。
异常处理:每个命令都用 try-catch 块包裹,以捕获解析中的错误并输出出错信息。
数据存储结构:questionBank 存储题库(题目 ID 与题目对象的映射)。
testPapers 存储试卷信息。
alerts 存储所有提示信息。
answerSheets 存储学生的答卷。
studentMap 存储学生信息(学生 ID 与姓名的映射)。
deletedQuestions 存储删除的题目 ID。
2. 各种解析方法
这些方法解析不同的输入指令并将数据存储到相应的结构中。
parseQuestion:解析题目定义(#N:)并存储在 questionBank 中。
parseTestPaper:解析试卷定义(#T:),并检查试卷中是否包含不存在的题目,若存在则添加到 outputBuffer 中。还检查试卷总分是否为100分,如果不为100则生成提示。
parseStudentInfo:解析学生信息(#X:),并存储在 studentMap 中。
parseAnswerSheet:解析学生答卷(#S:),若题目已被删除则将其得分标记为 0。
parseDeletedQuestion:解析删除题目(#D:N-),将题目 ID 添加到 deletedQuestions 中,同时更新已有答卷标记为已删除。
displayAlerts 和 displayAnswersAndScores:显示提示信息和学生的答题情况与总分数。
3. Question 类
Question 类用于定义题目,包括题目 ID、内容和正确答案。
validateAnswer 方法用来检查学生的回答是否正确。
4. TestPaper 类
TestPaper 类表示一个试卷。
数据结构:questions 用于存储题目和对应的分数。
outputBuffer 存储错误提示(如不存在的题目信息)。
totalScore 表示试卷的总分数。
主要方法:addQuestion 和 addNonExistentQuestion 用于添加题目(包括处理不存在的题目)。
getTotalScore 获取试卷的总分数。
5. AnswerSheet 类
AnswerSheet 类代表一个学生的答卷。
数据结构:userAnswers 存储学生的回答。
correctness 存储回答的正确性。
scores 存储各题的得分。
isDeleted 存储题目是否被标记为删除。
主要方法:recordAnswer:记录学生的回答,并判断题目是否已被删除。
markQuestionAsDeleted:将题目标记为已删除。
displayAnswerContent:显示每道题目的答题情况,包括题目内容、学生的回答、正确性。
displayScores:根据学生的答卷计算总分并显示。
采坑心得
1.题目不存在的处理
在 parseTestPaper 中,当题目不存在时,需要记录 non-existent question~0,而不是跳过或抛异常。
parseAnswerSheet 方法也可能遇到引用不存在题目的情况,需要提前校验题目是否存在。
心得:
在 parseTestPaper 中,直接检查 questionBank 是否包含题目,若不存在则生成对应的警告信息。
确保在 AnswerSheet 初始化时只处理合法题目或添加逻辑以标记无效题目。
- 删除题目后分数及状态的处理
坑点:我的输入为如下图时
我最后输出的总分是8分
所以:在解析删除题目时,可能忘记将相关答题分数清零,导致计算总分出错。
在解析学生答题信息时,删除的题目应该被标记并设置得分为 0。
心得:
在 markQuestionAsDeleted 中确保将 isDeleted 和 scores 数组中的相应索引正确更新。
在 displayAnswerContent 和 displayScores 方法中分别处理已删除题目,避免其影响答题内容输出和最终分数。
4.空答案和答题内容显示
坑点:
未回答的题目可能为空,直接访问 userAnswers[i] 可能导致 NullPointerException。
displayAnswerContent 方法中未处理空答案,可能导致结果输出不完整。
心得:
定义常量 NO_ANSWER,处理所有未回答的情况,避免空指针异常。
使用三元运算符检查 userAnswers[i] 是否为 null,提供默认值确保输出格式一致。
5.学生答题信息的索引与题目编号映射
坑点:
学生答题信息中的题目编号可能与试卷中的实际题目顺序不一致,直接按顺序记录可能导致数据错位。
答题记录时,若试卷题目数和答题数据数量不匹配可能会出现 IndexOutOfBoundsException。
心得:
确保题目索引与编号映射正确,使用 orderIndex 作为题目在答卷中的索引。
记录学生答题时先进行 index 范围检查,确保 orderIndex 在题目数组大小内。
改进建议
1增强正则表达式的鲁棒性
使用正则表达式解析字符串时,复杂的表达式可能使得代码难以理解和调试。可以考虑将复杂的正则表达式提取为常量,并添加详细的注释来解释其每个部分。
比如,可以在类的开头定义正则表达式常量:
private static final Pattern QUESTION_PATTERN = Pattern.compile("#N:(\\d+)\\s+#Q:(.+?)\\s+#A:(.+)"); private static final Pattern TEST_PATTERN = Pattern.compile("#T:(\\d+) ((\\d+-\\d+ ?)+)");
2. 优化 parseTestPaper 方法
目前,parseTestPaper 方法中添加了 addNonExistentQuestion 来处理缺失问题的情况。可以考虑使用一个 Optional
点击查看代码
private static void parseTestPaper(String inputLine, Map<Integer, TestPaper> testPapers, Map<Integer, Question> questionBank, List<String> alerts) {
// Parse and handle test paper ID and questions
...
addQuestionToTestPaper(parts, testPaper, questionBank);
}
private static void addQuestionToTestPaper(String[] parts, TestPaper testPaper, Map<Integer, Question> questionBank) {
for (String part : parts) {
int questionId = Integer.parseInt(part.split("-")[0].trim());
int score = Integer.parseInt(part.split("-")[1].trim());
questionBank.getOrDefault(questionId, Optional.of(new Question(0, "non-existent question", "N/A")))
.ifPresent(question -> testPaper.addQuestion(question, score));
}
}
点击查看代码
class Answer {
private String userAnswer;
private boolean isCorrect;
private int score;
private boolean isDeleted;
public Answer(String userAnswer, boolean isCorrect, int score, boolean isDeleted) {
this.userAnswer = userAnswer;
this.isCorrect = isCorrect;
this.score = score;
this.isDeleted = isDeleted;
}
// Getters and setters
}
private static void parseQuestion(String inputLine, Map<Integer, Question> questionBank) throws Uin:
增强正则表达式的鲁棒性
使用正则表达式解析字符串时,复杂的表达式可能使得代码难以理解和调试。可以考虑将复杂的正则表达式提取为常量,并添加详细的注释来解释其每个部分。
比如,可以在类的开头定义正则表达式常量:
private static final Pattern QUESTION_PATTERN = Pattern.compile("#N:(\\d+)\\s+#Q:(.+?)\\s+#A:(.+)"); private static final Pattern TEST_PATTERN = Pattern.compile("#T:(\\d+) ((\\d+-\\d+ ?)+)");
2. 优化 parseTestPaper 方法
目前,parseTestPaper 方法中添加了 addNonExistentQuestion 来处理缺失问题的情况。可以考虑使用一个 Optional
点击查看代码
private static void parseTestPaper(String inputLine, Map<Integer, TestPaper> testPapers, Map<Integer, Question> questionBank, List<String> alerts) {
// Parse and handle test paper ID and questions
...
addQuestionToTestPaper(parts, testPaper, questionBank);
}
private static void addQuestionToTestPaper(String[] parts, TestPaper testPaper, Map<Integer, Question> questionBank) {
for (String part : parts) {
int questionId = Integer.parseInt(part.split("-")[0].trim());
int score = Integer.parseInt(part.split("-")[1].trim());
questionBank.getOrDefault(questionId, Optional.of(new Question(0, "non-existent question", "N/A")))
.ifPresent(question -> testPaper.addQuestion(question, score));
}
}
class Answer {
private String userAnswer;
private boolean isCorrect;
private int score;
private boolean isDeleted;
public Answer(String userAnswer, boolean isCorrect, int score, boolean isDeleted) {
this.userAnswer = userAnswer;
this.isCorrect = isCorrect;
this.score = score;
this.isDeleted = isDeleted;
}
// Getters and setters
}
然后在 AnswerSheet 中,可以用 List
4. 改善异常处理
当前 main 方法中是通过捕获 Exception 并输出错误来处理异常的。可以为每个解析函数定义特定的异常类,以更精确地处理不同错误。
比如,可以为 parseQuestion 中的格式错误抛出特定异常:
点击查看代码
private static void parseQuestion(String inputLine, Map<Integer, Question> questionBank) throws InvalidFormatException {
Matcher matcher = QUESTION_P
private static void parseQuestion(String inputLine, Map<Integer, Question> questionBank) throws InvalidFormatException {
Matcher matcher = QUESTION_PATTERN.matcher(inputLine);
if (matcher.matches()) {
int questionId = Integer.parseInt(matcher.group(1));
questionBank.put(questionId, new Question(questionId, matcher.group(2).trim(), matcher.group(3).trim()));
} else {
throw new InvalidFormatException("Invalid question format: " + inputLine);
}
}
6. 使用流简化列表操作
可以使用 Java 8 的 Stream API 来简化 displayAlerts 和 displayAnswersAndScores 方法中的循环逻辑。例如,可以用 stream().forEach 来替换 for 循环,使得代码更清晰:
private static void displayAlerts(List<String> alerts) { alerts.forEach(System.out::println); }
7. 重构展示方法
displayAnswerContent 和 displayScores 方法可以封装成 AnswerSheet 的 toString 方法,这样便于测试和调试。
8. 优化输出逻辑
通过 StringBuilder 实现 displayScores 和 displayAnswerContent,这样可以提高输出时的效率。此外,避免在循环中频繁使用 System.out.println,可以在内部构造完整输出后一次性打印。
总结
通过三次实验,每一次的作业的迭代,我学习到了多种数据结构的灵活使用:
通过 Map、List 等数据结构来组织存储题目、试卷和答卷信息,了解了如何通过组合多个容器实现复杂的数据管理。
特别是通过 Map<Integer, Question> 和 List
正则表达式解析复杂输入:
使用正则表达式来解析结构化输入,掌握了如何使用正则表达式从复杂的文本中提取特定的字段。同时也了解到正则表达式在处理复杂输入时的一些挑战,例如正确编写模式并处理不同的输入格式。
异常处理的重要性:
在处理不确定输入时,增加了异常处理机制,以便于当输入格式不符合预期时,可以捕获异常并输出错误信息。学会了如何通过 try-catch 结构保证代码的鲁棒性。
类的设计与封装:
将题目、试卷和答卷分成 Question、TestPaper 和 AnswerSheet 类,这样每个类都能专注于自己的功能,符合面向对象编程的设计思想,提高了代码的可读性和复用性。
学会了如何设计类的构造方法、封装属性,以及为类方法定义合理的参数和返回值,来保证类的单一职责和高内聚。
数据一致性与验证:
为了避免数据不一致问题,代码中对试卷总分进行了验证、对答卷中的问题是否存在进行了检查。学习到在实现功能时,数据完整性检查是保障代码稳定性的重要部分。
逻辑的分层与清晰性:
将不同的逻辑分开,例如解析题库、解析试卷、解析答卷等方法,这种模块化设计有助于代码维护,也让每个方法职责更清晰。理解了如何通过方法分层来组织代码逻辑,提高了代码的结构化水平。
输出与展示:
在成绩显示中,通过格式化和拼接输出字符串来生成结构化的输出内容,学习到如何通过 StringBuilder 和格式化输出的方法提高效率,并保证输出内容的清晰易读。