第一次Blog作业——Java题目集1~3的总结与分析
题目集1-3总结性Blog
前言
在本阶段的学习过程中,我们完成了Java题目集1至题目集3的任务。知识点的设置上,覆盖了程序设计基础、类的设计原则及设计、类和对象的使用、类间的关系、排序的方法、Java集合框架的List等接口、正则表达式。
三次题目集,题目从五道到四道到三道,题量适中,前面几题都较为简单,最后一题难度逐渐递增,使我们渐入佳境,帮助我们巩固课堂上学习的概念,并通过实践加深理解。
本文将对这三个题目集进行总结回顾,包括设计与分析、遇到的问题及解决策略,以及针对未来改进的一些建议。
设计与分析
题目集1的最后一题
题目概述:按一定的格式输入题目数量、题目内容、答题信息,其中答案的顺序号与题目题号对应。输出答题信息和判题信息。
类图展示:
设计思路:因为给出的题目是全部的答卷中的题目,可以简单地分成三个类,题目类用来封装单个题目,试卷类用来存所有的题目,答卷类存答题信息。
其中,题目类包含题号、内容、正确答案,还需要有判断单个题目正误的方法。试卷类要有题数、题目列表属性,还要有添加题目的方法,又因为题目不按顺序输入,需要试卷类对所有的题目按题号排序并返回的方法。答卷类存储答案列表,维护判题列表以便输出,还要有判断所有答案正误的方法。
细节上,要注意题目信息可以含空格符,这意味着不可以用空格分割字符串。
源码分析:
(1)顺序图:
(2)输入的处理:因为没有打乱题目和答案的顺序输入,所以在第一次的代码中,我把每一行字符串按顺序读入,分别处理。
题目的处理上,我用String[] parts=line.trim().split("#[NQA]:");用正则表达式#[NQA]:,避免了把题目里有空格的部分分开。答卷的处理上,我用了类似的方法,String[] answers=line.trim().split("#A:");
(3)题目的排序:Collections.sort(questions, Comparator.comparingInt(Question::getqNum));
或者Collections.sort(questions, (q1, q2) -> Integer.compare(q1.getqNum(), q2.getqNum()));
(4)单题的判断:用了字符串的比较函数equals,答案要去掉首尾的空格再比较。
(5)全部题目的判断:遍历题目列表,比较单题的答案,将结果存入判题列表。
性能评估:
可以看到各项指标都在适宜的范围内。
因为题目简单且设计合理,最大的圈复杂度也只有4。
题目集2的最后一题
题目概述:打乱顺序混合输入题目信息、试卷信息、答卷信息,试卷题目的顺序与题号可以不一致,试卷答卷可以有多份,答案的顺序与试卷中题目的顺序相对应,输出总分不足100警示、错误的试卷号或者答案为空、答卷信息、判题信息。
类图展示:
设计思路:
设计了七个类,输入类用来处理输入,建立信息,题目类放单个题目,题目集类中存放所有的题目,题目集中的一部分题目组成了试卷类,多张试卷保存在试卷册类中,答案类存一个答卷信息,答卷集存所有的答卷信息。
其中,题目类包含题号、内容、正确答案,还需要有判断单个题目正误的方法。题目集类似于第一次大作业中的试卷类,要有题数、题目列表属性,还要有添加题目和获取题目的方法。试卷类存储试题和分数的映射关系,能够检查总分。试卷集类存储所有试卷号和试卷的映射关系。答卷类存储答案列表,维护判题和分数列表以便输出,还要有判断合法性和判断所有答案正误的方法。答案集类有改所有答卷的方法。
这种映射关系的存储用ArrayList是麻烦而低效的,在第二次大作业,我改变了数据结构,选用了LinkedHashMap来存储。键值对的存储可以用Map接口来提供,又因为答卷的顺序和试卷对应,输出的顺序也与输入的顺序有关联,存储上不应该被自动排序,也不能不保证排序,所以选用LinkedHashMap。
源码分析:
(1)顺序图:
(2)输入的处理:单独抽出来了一个类进行处理,因为是打乱顺序输入,所以判断每一行的特征信息,分别进行处理,读到”end”结束。因为没有格式错误的用例,所以可以直接用字符串的charAt函数判断每一行的特征字符N、T、S,或者用startsWith函数判断开始字符串,分别用split函数分割成各种细节信息进行存储进题目集、试卷集、答案集备用。
(3)关于这些映射关系的建立:封装一些类内的方法,内部具体是用put方法存放键值对,用getOrDefault方法获取某键的某值,用containsKey方法检查映射中是否包含指定的键。
(4)试卷总分的判断:遍历所有试卷,调用它的检查试卷总分函数。
(5)所有答卷的处理:遍历所有答卷,先调用他们的判断试卷号方法,当试卷存在才具体进行判题和输出。
(6)单张答卷的判题:遍历所有题目,先处理空答案,再判断单题,记录得分和判决结果,维护输出信息。
性能评估:
可以看到平均圈复杂度为1.45,但是最大圈复杂度有8,在本题核心的checkAnswer方法中。
3.题目集3的最后一题
题目概述:打乱顺序输入题目、试卷、学生、删除题目信息,题目输入顺序与题号不相关、允许题号缺失,答案数量可以大于或少于题目数量,答卷格式中答案前是试卷题目顺序号,输出总分警示、答卷、判分、题目被删除、引用错误、格式错误的信息。
类图展示:
设计思路:
类的设计沿用了大作业二,新增了学生类、学生表类来存储学生的学号、姓名信息。本次对代码的健壮性要求较高,有一系列对格式及引用错误的判断,所以改用正则表达式来判断更为简便。
源码分析:
(1)输入处理:采用正则表达式来判断格式错误和分离各类信息,用捕获组来获取细节信息来存储。
(2)各类信息的处理思路:
遍历检测,先判断答案是否为空。
否则,再判断试卷错误引用(题库中没有,试卷中有),输出non-existent question~0。
否则,判断问题被删除(题库中有,试卷中有,但是isdelete标志为true),输出the question 题号 invalid~0。
正常的判断输出答案。
(3)顺序图:
性能评估:
最大圈复杂度有10,在本题核心的checkAnswer方法中,还有对输入信息的处理InputBuild方法圈复杂度也较高,有8,在后面的改进分析中我进行了优化,如下。
采坑心得
- 问题描述:在第一次大作业中,我用"#[NQA]:"进行分割后,无法正确存储和得到正确答案。
解决思路:设置断点,进行调试。
代码分析:根据这种方法进行分割,#前面会有空字符串,下标要从1开始。
String[] parts=line.trim().split("#[NQA]:");
Question q=new Question(Integer.parseInt(parts[0].trim()),
parts[1].trim(),parts[2].trim());
2.问题描述:第一次大作业,在eclipse上测了所有的测试样例正常后,复制到PTA上,全部非零返回。
解决思路:在eclipse上又调了很久,无果,痛苦地发现原来是把package一起复制过来了。但是也是有好处的,之后写代码,偶尔还是会忘记,但是第一反应会去看看package是不是被我复制过来了。
代码分析:去掉package。
3.问题描述:对类进行排序。
解决思路:如果希望类的对象可以根据类中的某个字段自然地被排序,可以让这个类实现Comparable接口,并重写compareTo方法。
比如在题目集2的手机类的排序:
class MobilePhone implements Comparable<MobilePhone>{
//其他属性方法省略
public int compareTo(MobilePhone other) {
return Integer.compare(this.price, other.price);
//return this.price-other.price;
}
}
Collections.sort(mp);
如果需要根据不同的条件对同一个类进行排序,或者不想修改类本身,可以使用Comparator。
public static Comparator<MyClass> valueComparator = new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
return Integer.compare(o1.getValue(), o2.getValue());
}
};
// 使用 Comparator 对列表进行排序
List<MyClass> myList = new ArrayList<>(); // 假设已填充了对象
Collections.sort(myList, MyClass.valueComparator);
4.第二次题目集从提交来看是比较顺利的,基本上交了就过了。但是提交之前并不轻松,认真分析了题目的细节,慢慢地设计了类,在自己的编译器上也调了很久。
其中,在第二题的提交中,我感受了DecimalFormat方法的用法。
它参数的模式字符的含义如下:
public String getArea() {
DecimalFormat df=new DecimalFormat("0.00");//#.00 模式不会显示前导零,而 0.00 模式总是会显示整数部分的一个零
//return String.format("%.2f", Math.PI*radius*radius);
return df.format(Math.PI*radius*radius);
}
5.问题描述:循环输入部分用正则表达式表示出来,处理的时候用捕获组循环处理,但是无法得到正常结果。
解决思路:经过调试,发现捕获组只能匹配到重复出现的最后一节。我将重复的部分捕获成一个组,进一步用正则表达式一点一点匹配。或者也可以捕获成一组,用split分割也可以。
代码分析:
我使用了第一种方法:
if(m.find()) {
int Tnum=Integer.parseInt(m.group(1).trim());
Test test=new Test(Tnum);
//捕获所有的题号分数
String QSpart=m.group(2);//傻瓜怎么在这里加了个trim,有格式的不要乱去空格
Matcher m2=Pattern.compile(" (\\d+)-(\\d+)").matcher(QSpart);
while(m2.find()) {
test.saveQuestionScores( Integer.parseInt(m2.group(1).trim() ), Integer.parseInt(m2.group(2).trim() ));
}
testBank.saveTests(Tnum,test);
}
6.问题描述:题目集3的最后一题提交最后一个测试点一直不过,怀疑是正则表达式对条件的限制有问题。
解决思路:去掉空格->超级多测试点过不了了->找原因->修改正则表达式->通过所有测试点,答案正确->反思确定是因为答案可以有空格。
代码分析:抛开样例的测试点,把去掉空格之后的错更多的代码改对的过程我遇到的问题:
如果只去掉正则表达式[^# ]的空格,会错更多,因为那个*会贪婪地匹配字符,包括空格,前面格式中还有个空格,空格被*吃掉了,就匹配不上格式了。同时满足答案可以为空和答案中可以有空格,需要改成如下格式:
改进建议
- 题目集1的最后一题,不太优雅的部分是输入的处理在主函数,稍微有点长,但是也可以接受,后面几次就单独抽出来做了一个类。
- 圈复杂度有点高,可以进行改进。虽然在总结的过程中,通过抽象成函数的方法将作业三的最高圈复杂度从10降到了8,但其实还是有很大的优化空间的。在之前的编写代码中,没有特别重视,之后会多加考虑。
总结
经过这一系列练习,我在编程技能上有了显著的进步。不仅掌握了Java的日期类、正则表达式、Java集合框架等知识点,而且也锻炼了解决实际问题的能力,特别是面向对象的编程思想,在代码的一次次迭代中,一点点地融入实践中,受益匪浅。但是对更深入更复杂的知识还不熟练,需要多学习多思考多练习。
老师上课很热情很负责、课堂课下作业以及实验很全面(有一点多,但还好)、课上课下组织方式也不错,很喜欢,感谢老师的辛勤付出,也很期待接下来的学习!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~