pta1-3次大作业博客总结
一、前言
在前三次的PTA大作业中,我们依次完成了答题判题程序1至3。这三个程序彼此紧密相连,层层递进,每一次的完成都是在前一次基础上的持续修改、完善与优化。这三次大作业的顺利进行要求我们对类、Map、List、正则表达式等知识点有深入的理解与熟练的运用。从一开始,我们就需要构建一个合理的结构,因为这三个题目是紧密相扣的。
1.答题判题程序1
答题判题程序1的任务是模拟一个小型的测试系统,要求输入题目信息和答题信息,并根据题目信息中的标准答案来判断答题的结果。这个过程不仅需要准确地解析和处理输入数据,还要求我们设计合理的算法来评估答案的正确性。
为了顺利完成这个题目,我们必须熟练掌握多种知识点,包括类的使用、字符串的分割、以及集合框架中的Map和List等。该题目的题量和难度均为中等,适合用来锻炼我们的编程能力和逻辑思维。
对我而言,作为一名从未接触过面向对象编程的初学者,这个任务在最初接手时确实显得有些棘手。面对面向对象的概念、类的设计以及如何高效地存储和处理数据,我需要花费额外的时间进行理解和练习。然而,经过不断的努力和尝试,我逐渐掌握了这些知识,并成功地完成了这个程序。
2.答题判题程序2
答题判题程序2相比于答题判题程序1新增了试卷信息模块。为顺利完成此题,我们同样需要熟练掌握类的使用、字符串的分割操作,以及集合框架中的Map和List等。这个题目的题量以及难度都属于中等。
3.答题判题程序3
答题判题程序3在答题判题程序2的基础上新增了学生信息管理和题目信息删除功能。除了前面提到的知识点外,要完成此题目还需我们熟练掌握正则表达式,以判断输入格式的正确性。相比前两次PTA任务,第三次因新增了更多输入输出判断,题量和难度均大幅提升,完成此任务需要投入较多的时间和精力。
二、设计分析
1.答题判题程序1
本题目要求设计一个答题程序,用于模拟小型测试。程序需输入题目信息和用户答题信息,并依据题目信息中的标准答案判定答题结果。
为实现此功能,我们设计了四个核心类:
- 题目类:用于封装每道题目的信息,包括题目描述、选项(如有)、标准答案等内容,便于统一存储和访问。
- 试卷类:用于管理所有题目实例,将题目信息集中封装,方便对整个题目集合进行操作。
- 答卷类:用于封装用户的答题信息,将用户的每个答案与对应的标准答案比对,以便进行评判和记录。
- 主类:作为程序的入口,负责接收并解析输入的题目信息和答题信息,调用上述各类的功能,实现题目加载、答题记录、结果计算等功能。
通过这四个类的协作,程序可以有效地管理题目、记录答案,并计算出测试结果,使整个答题流程清晰流畅。
类图
时序图
1.题目类
该类用于表示单个题目,包含以下属性:题目编号、题目内容和标准答案。类中定义了多种方法,支持基本的操作,例如设置和获取题目内容。此外,提供了答案校验方法,用于判断用户的回答是否正确。
//判断答案是否正确
public boolean isCorrect(String answer) {
return standardAnswer.trim().equals(answer.trim());
}
2.试卷类
该类用于表示一组题目集合,使用 Map
结构来存储多个题目。类中包含了一些基础操作方法,如添加题目、删除题目、查找题目等。
3.答卷类
该类用于存储用户输入的答案、试卷信息及判题结果。类中包含以下主要属性:paper
(试卷类实例)、Map
类型的答案集合、以及 Map
类型的判题结果集合。主要操作包括保存答案、进行判题等功能,其中判题方法是该类的核心功能,用于对比用户答案与正确答案并生成结果。
判题方法首先通过 for
循环遍历试卷中的每个问题,使用 paper.getAllQuestions()
获取所有问题列表并依次进行评估。对于每个问题,从 answers
中提取用户的回答,并检查是否为空 (answer != null
)。最终,所有题目的评估结果都会记录在 results
集合中。
// 判题方法
public void evaluateAnswers() {
for (Question question : paper.getAllQuestions()) {
String answer = answers.get(question.getQuestionNumber());
if (answer != null) {
boolean result = question.isCorrect(answer);
results.put(question.getQuestionNumber()-1, String.valueOf(result));
} else {
results.put(question.getQuestionNumber()-1,"false");
}
}
}
}
4.主类
该类综合调用了前面提到的三个类,用于实现题目的完整功能。其主要职责是接收用户输入,解析题目信息和答卷数据,并将其传递给相关模块进行处理,从而完成对题目的管理和答卷评判等任务。
2.答题判题程序2
答题判题程序2在答题判题程序1的基础上新增了试卷信息,并且这三种信息可能会被打乱顺序混合输入。
为实现此功能,我在答题判题程序1的基础上进行了以下改进:
- 试卷类:增加了试卷编号、分数和总分等属性。
- 答卷类:新增了试卷有效性判断标志,并调整了答案评估函数。
- 主类:显著优化了输入解析代码,以支持信息的乱序处理。
类图
时序图
1.试卷类
增加了试卷编号、分数和总分等属性及其相关基本操作。
2.答卷类
对其核心函数答案评估进行了大幅修改
相比于答题判题程序1,答题判题程序2的答案评估函数进行了一下修改:
- 增加了试卷有效性检查:在评估答案前,新增了对试卷有效性的判断逻辑,如果试卷无效(
getEffectiveness() == -1
),直接输出提示并结束评估。 - 处理了答案可能为空的情况:如果答案为空,会记录相应的输出内容,且分数不会增加。
- 详细的输出和分数记录:对每道题的评估结果进行了详细输出,包括题目、答案和是否正确的标记(
question.getQuestion() + "~" + answer + "~" + isCorrect
)。同时,还将每道题的分数存入resultScores
列表,并最终输出各题分数及总分。 - 计算总分:通过遍历所有题目,计算并累积每道题的分数,最终输出总分。
- 逐题输出结果与分数格式化输出:输出每道题的评估结果以及分数列表,并使用格式化输出的方式将各题分数和总分进行组合显示。
3.主类
相比于答题判题程序1,答题判题程序2的主类优化完善了用户输入解析部分,进行了以下改进:
- 支持多个试卷和答卷的管理:通过
papers
和answerSheets
两个集合来管理多个试卷和答卷。 - 输入格式更加灵活:允许通过特定标识符(如
#N:
,#T:
,#S:
)来识别输入的不同类型(题目、试卷、答卷)。 - 增加了总分校验:创建试卷时,校验每张试卷的总分是否达到100分,如果不足,则会输出警告。
- 处理无效答卷:通过设置
Effectiveness
字段来标识答卷的有效性。如果试卷不存在或无效,程序会提示“试卷编号不存在”。 - 代码结构更清晰:改进的代码分离了题目解析、试卷解析和答卷解析的逻辑,使每个解析模块更加独立,逻辑更清晰。
3.答题判题程序3
答题判题程序3相比于前两次难度大幅提升,相比于前两次,第三次,第三次新增了学生信息、删除信息,以及大量格式判断。
为完成该题目,在前两次的基础上,进行了以下修改:
- 试卷类:新加了删除题目函数。
- 答卷类:对评估函数又一次进行了完善以及优化。
- 学生类:新增了学生类,存储学生信息。
- 题库类:新增了题库类,存储题目。
- 匹配类:新增匹配类,判断字符串是否合法。
类图
时序图
1.答卷类
又一次对评估答案函数进行了进一步完善与优化:
- 增强了对试卷和题库的有效性检查:在
evaluateAnswers
方法中增加了对questionBank
和paper
对象的空值检查,确保这些对象已正确初始化,避免了可能的空指针异常。 - 细化的评分处理:在循环中根据不同情况将各题得分结果存储在
resultScores
中,不仅计算总分数,还将各题的分数单独存储,便于后续分析。 - 改进输出结构:在输出总成绩时,添加了学生的基本信息(学号和姓名)的判定。
- 支持非标准情况的处理: 加入了对题号不存在、题目被删除、题目不在试卷中的判断条件。针对无效试卷的情况,设置了输出“non-existent question~0”和“the question X invalid~0”。
2.学生类
定义了属性学号姓名及其基本操作。
3.匹配类
匹配类中定义了isVaild方法,使用正则表达式判断输入是否合法。
public boolean ifvaild(String line) {
// 匹配题目
if (Pattern.compile("^#N:\\d+ #Q:.+ #A:[\\w\\d]+.*$").matcher(line).matches()) {
return true;
}
// 匹配试卷
else if (Pattern.compile("^#T:\\d+(\\s\\d+-[\\d\\w]+)*\\s*$").matcher(line).matches()) {
return true;
}
// 匹配答卷
else if (Pattern.compile("#S:(\\d+) (\\d+) (.+)").matcher(line).matches()) {
return true;
}
// 匹配学生
else if (Pattern.compile("^#X:\\d{8} \\w+(-\\d{8} \\w+)*$").matcher(line).matches()) {
return true;
}
// 匹配删除题目
else if (Pattern.compile("^#D:N-\\d+$").matcher(line).matches()) {
return true;
}
// 如果都不匹配,则返回false
return false;
}
三、踩坑心得
- 在编写答题判题程序2时,我始终无法通过答卷有效性判断的测试点,于是加了一个属性effectiveness来判断答卷有效性。
private int effectiveness;
if (getEffectiveness() == -1) {
System.out.println("The test paper number does not exist");
return; // 如果有效性为 -1,则直接返回,不再评估答案
}
-
在编写答题判题程序1和2时,由于对如何匹配正确的字符串格式不够清晰,我采用了相对复杂的方法来实现这一功能,通过使用substring以及split以及循环来分解输入的字符。
-
由于在编写答题判题程序1和2时完全没有考虑输入格式的问题,因此在开发答题判题程序3时,出现了许多测试点返回非零值的情况。
-
在编写答题判题程序3时,无效试卷的引用信息与输出的题目信息之间持续发生冲突,这导致我始终无法通过测试点4。
// 题号是否在题库中存在
else if (!questionBank.getQuestionBank().containsKey(questionPaper.getQuestionNumber())
|| question.getQuestion().equals("-1")) {
output = "non-existent question~0";
score = 0;
}
// 題目是否被刪除
else if (question.getQuestionNumber() == paper.getDeleteNum()) {
score = paper.getQuestionScore(question.getQuestionNumber());
output = "the question " + question.getQuestionNumber() + " invalid~0";
}
- 在编写答题判题程序3时,删除题目时直接将其移除,导致无法将被删除题目的分数赋为0。为了解决这个问题,我首先将所有题目的分数初始化为0,随后在遍历时再将未删除题目的分数重新赋值。
// 试卷所有分数置0
for (int score : paper.getScoreMap().values()) {
score = 0;
resultScores.put(j, score);
j++;
}
-
由于答题判题程序1和2的难度相对较低,设计上相对简单,整体结构仅包含三个类,功能划分也并不十分细致。这种设计使得答卷类承载了过多的内容,导致在编写答题判题程序3时遇到了诸多挑战。随着第三次题量的大幅增加,程序需要处理的格式判断变得复杂,新增的内容和类也相应增多,修改和调整的地方也非常多。为了解决这些问题,不得不花费大量的时间进行重新设计和优化,以确保系统能够高效、准确地处理更多的题目和复杂的判断逻辑。
-
答题判题程序1和2的代码注释相对较少,这给后续的开发工作带来了困难。在编写答题判题程序3时,我时常需要回顾之前的代码,以便更好地理解某些功能块的具体作用和实现逻辑。缺乏详尽的注释使得我在阅读代码时无法迅速捕捉到每一段代码的意图,增加了开发过程中的时间成本。
四、改进建议
- 确保注释尽可能详细,以便于后续的阅读和修改。在代码中使用清晰的注释能够帮助自己快速理解代码的功能、逻辑和设计意图。每个函数、变量和重要的逻辑分支都应有相应的解释,以便于自己能够轻松跟踪和维护代码,方便后续的多次开发。
- 在设计类时,应尽量追求精细化,切忌将所有功能和属性堆砌在一个类中。这种做法不仅会导致类的复杂性增加,还会使得后续的开发和维护变得极为困难。一个过于庞大的类难以理解和测试,容易引发错误。合理划分职责、创建小而专注的类,可以提升代码的可读性和可维护性,使得每个类都承担特定的功能。这样一来,修改或扩展某一部分的功能时,只需关注相关的类,提高了开发效率。
- 掌握正则表达式的使用非常重要,它能够显著提高编程效率并使代码更加简洁,能够高效地处理字符串的搜索、匹配和替换等任务。通过正确运用正则表达式,可以减少冗余代码,从而简化逻辑和优化程序性能。
- 扎实基础知识是编写高质量代码的关键,特别是在数据结构的应用上。正确和合理地使用如 Map、List 和队列等数据结构,可以极大地提升代码的结构性和可维护性。掌握这些基本的数据结构,不仅能帮助你解决实际问题,还能使你的代码逻辑更加清晰,结构更加合理,从而为后续的开发和维护打下坚实的基础。
- 在编写代码时,应充分考虑各种可能的情况,尤其是要防止异常值的输入。这种前瞻性的思维能够有效提高代码的健壮性和稳定性。处理输入时,进行严格的验证和筛选是至关重要的,以确保程序能够正确处理用户输入的各种形式。
五、总结
在此次这三次大作业中,我们逐步完成了三个答题判题程序的开发,每个程序都是在前一个的基础上进行了改进与扩展。这几次大作业不仅加深了我对面向对象编程、数据结构和算法设计等知识的理解,还提升了我解决实际问题的能力。
在第一个答题判题程序中,要求设计一个基本的测试系统,输入题目信息和答题信息,并通过标准答案进行判断。这一过程要求我们熟练掌握类的设计、字符串的处理及集合框架的运用。作为一个初学者,我在学习如何设计类及其交互时遇到了一些困难,但在不断的实践中,我逐渐掌握了相关的知识并成功实现了程序功能。
第二个程序在第一程序的基础上增加了试卷信息模块,处理复杂输入的需求。我在设计时,增加了试卷编号、分数等属性,并优化了输入解析的代码,以支持信息的乱序输入。这一改进不仅提升了程序的灵活性,也让我对程序设计的模块化有了更深的理解。
进入到第三个程序时,我面临了更大的挑战。新增的学生信息管理、题目信息删除功能以及格式判断使得第三次大作业的复杂性大幅提升。在设计中,我创建了多个新类以分担各自的功能。此外,我运用正则表达式对输入格式进行验证,确保程序在接收数据时的严谨性。这一过程让我深刻体会到基础知识的重要性,特别是在数据结构和正则表达式的运用上。
通过这三次大作业,我不仅提升了编程技能,还培养了分析问题和解决问题的能力。遇到的诸多挑战,比如如何有效管理输入输出、如何进行数据的有效性检查、如何处理对象之间的关系,都促使我不断反思和调整我的设计思路。在此过程中,我也意识到注释的重要性,合理的注释不仅能帮助我理解代码,也为他人阅读和维护代码提供了便利。这次三次大作业让我在实践中不断学习和成长,理解了软件开发过程中的重要环节,如设计、实现、测试和维护。未来,我会继续加强这些方面的学习,尤其是提高代码的可读性与可维护性,力求在编写高质量代码的道路上不断前行。
原文链接:BLOG-1
暂无评论内容