(1)前言
前三次作业算是让我走上了写JAVA的正轨,题目小多,难度还可。初步学会了如何去写了类。题目内容主要涉及无参有参构造,排序查找,类的设计,常用系统类方法的使用等等,以及主要的答题判题程序的编写与迭代。在这三次作业里也算是初窥门槛了罢。
(2)设计与分析
第一次作业
答题判题程序一
先根据题目要求把Question,TestPaper,AnswerSheet 三个类写出来,再想想里面属性方法的设计,根据老师讲的要“拎得清”原则和个人的设计思路,题目也把属性方法推荐出来了,所以先试试了。
然后加上PTA的神秘Main,代码由四个主要部分组成:
Question 类:用于表示试题,包括题号、内容和标准答案。
TestPaper 类:用于管理试题的集合,支持添加和获取试题。
AnswerSheet 类:用于处理用户的答案,进行答案检查并输出结果。
Main 类:程序的入口,负责与用户交互,收集并处理输入/输出。
具体的第一步设计如下:
Question 类
属性:
num:题号
content:题目内容
standardAnswer:标准答案
作用:封装题目信息,提供对题目的访问。
TestPaper 类
属性:
题目集合存储多个 Question 对象。
方法:
saveQuestion(num, question):保存题目到集合中。
getQuestionCount():返回题目的数量。
作用:管理整个试卷的题目,提供添加和查询题目的功能。
AnswerSheet 类
属性:
testPaper:关联的试卷对象。
answers:存储用户的答案。
checkResults:存储判题结果(如正确与否)。
方法:
saveAnswer(i, answer):保存用户提供的答案。
checkAnswer(i):判题,比较用户答案与标准答案是否一致。
printResults():输出所有题目的内容、用户答案及判题结果。
作用:处理用户的答案并进行判题,提供输出功能。
Main 类
作用:作为程序入口,负责:
创建 TestPaper 和 AnswerSheet 对象。
读取用户输入的题目数量和题目信息。
读取用户输入的答案。
调用判题和输出结果的方法。
功能流程分析
输入题目数量:用户首先输入试卷中的题目数量,这是程序的初始输入。
输入题目信息:接下来,程序读取题目信息,包括题号、内容和标准答案。为了方便处理,采用指定的格式(如 #N:、#Q:、#A:)进行分割。
输入用户答案:用户提供的答案同样以特定格式输入,程序解析并保存到 AnswerSheet 对象中。
判题功能:程序通过 checkAnswer 方法逐个题目进行判定,并记录结果。
输出结果:最后,调用 printResults 方法输出题目、用户的回答以及判题结果,提供直观反馈。
根据分析先把scanner给写出来,我已经习惯偷个小懒直接写import java.util.*;,这样后面要用MAP之类的就不用再写了。。
把题目和答案往List里一放,输入一存一判断写个For循环输出基本写完了,最后发现还有空格判断过不去,上网学习一下trim方法,写上搞定。(PS:多学点方法真的能方便很多)
第二次作业
答题判题程序二
这个是在答题程序一的基础上实现功能的细化与增加。所以我们沿着程序一的设计思路写下去就行了。
首先这题一开始应该先从输入的处理开始分析,可能根据不同输入放入不同存储中,然后输出。总体实现功能为:
输入数据: 包括题目信息、试卷信息和答卷信息,可能混合输入。
输出数据: 对每张答卷进行判分,检查试卷总分,处理无效试卷号。
然后我根据这个去写方法。最后决定使用MAP来存储数据,在这里可以存题号和分数之类,比较适用。这样可以做到使每个题号对应一个包含题目内容和标准答案的结构。试卷号作为键,值是题目编号和分值的映射,以及记录每张答卷的试卷号和对应的答案列表。
同时要逐行读取输入数据,判断数据类型(题目、试卷、答卷)进行分类存储,所以使用while (sc.hasNextLine())判断作为输入的主体结构。
在处理试卷信息时,计算总分,并在该卷分值不为100时,生成警告信息,写一个boolean isFullScore100()方法。
对每张答卷,依照试卷信息中的题目顺序,逐一对比答案,记录结果,因为都放在MAP里,按次序一个个比对就行了。
然后输入的时候注意按要求格式输出每题的判题信息、得分情况以及对不存在的试卷号的提示。就是要按要求写对应逻辑判断。
比如用entry.getValue() 获取题目的得分,这是一个常见的数据结构,可用于存储与问题相关的分数。
List:outputList 和 scoresList 用于保存每道题的反馈信息和分数。
还有通过 parts[answerIndex].substring(3) 从提供的字符串中提取答案,假设答案字符串的格式为某种前缀加上实际答案。
判断答案是否存在,如果不存在,返回特定消息并赋予得分为0。
然后使用question.judgeAnswer(answer) 方法来判断用户答案的正确性,并根据结果决定得分。
并且把答案放入isCorrect布尔值中,用于表示用户的答案是否正确。
结果汇总时使用 String.join 将所有的分数连接为一个字符串,方便输出。
同时为了代码的健壮,用results 存储最终的所有输出信息,包括每个问题的反馈和总分。
最后,通过简单的循环输出results中所有警告信息和结果信息。
第三次作业
答题程序三
答题程序三又是在答题二的基础上优化,但是我答题二的设计上分工太繁杂了,所以在延续思路的同时对代码结构进行了拆分与细化,同时答题三里面输入类型变多,同时也有输入错误的判断,于是先学习了正则表达式再去完成错误输入判断。
所以我重新细化了一下代码的结构,在二的基础上重写了一部分变量和方法。
Question 类基本变量没什么变化,还是:
int id:编号。
String question:题目内容。
String answer:题目标准答案。
public Question(int id, String question, String answer)构造方法,用于初始化题目对象。
目的: 该类用于存储单个题目的信息,包括题号、题目内容和标准答案,以便后续评分时进行对比。
TestPaper 类我们需要加上试卷的编号,还是用MAP去存题目—分值。
int id:试卷编号。
Map<Integer, Integer> questionScores:题目编号与对应分值的映射。
用public TestPaper(int id)初始化试卷对象。通过public void addQuestion(int questionId, int score)向试卷中添加题目及其分值。
再用public int getFullScore()来计算试卷的总分。
这样我的question类就可以管理一张试卷,能够存储与题目相关的信息并计算总分,还有以便在评分时进行查询。
再写一个Student 类来存储学生的信息,这样能实现新加的有不同学生答题。
先设计两要输入的变量String id:学号,String name:学生姓名。
然后用public Student(String id, String name)初始化学生对象。
这样这个类就能用于存储学生的基本信息了。
ExamSystem 类我还是用MAP去处理各对象的关系,因为这样还是比较方便的。
Map<Integer, Question> questions:题目编号与题目对象的映射。
Map<Integer, TestPaper> testPapers:试卷编号与试卷对象的映射。
Map<String, Student> students:学号与学生对象的映射。
Map<Integer, Map<String, Map<Integer, String>>> answersCache:存储答题记录的映射。
Set
然后我们因为保留在主程序用while (sc.hasNextLine())在各种“#?”判断中轮询去写入信息的方式,所以写上各个不同开头进入的信息处理函数,按格式用split分割即可。
addQuestion(String input):处理题目信息输入。
addTestPaper(String input):处理试卷信息输入。
addStudent(String input):处理学生信息输入。
addAnswer(String input):处理答卷信息输入
deleteQuestion(String input):处理题目删除信息。
gradeAllAnswers():对所有学生的答案进行评分。
gradeAnswers(TestPaper testPaper, Student student, Map<Integer, String> answers)来实现具体评分逻辑。
所以这个类是整个考试系统的核心,负责所有信息的管理、输入处理和评分逻辑,主要逻辑在这个类里写,上面的几个类主要是存数据。
所以类之间的关系就很清楚了,ExamSystem 类包含了 Question、TestPaper 和 Student 类的实例。它通过这些实例来管理和操作题目、试卷和学生的信息。TestPaper 类通过 Map<Integer, Integer> 存储题目及其分值,与 Question 类建立联系。答题信息存储在 ExamSystem 类中,用于后续的评分处理。
然后Main的写的思路和答题判题程序二差不多,还是在输入里写while套判断进入对应函数去存数据,再通过方法处理输出。
(3)踩坑心得
第一次作业
答题判题程序一
多空格:
含多余的空格符。例如:
N: 1 #Q: 5 +5= #A:10
A:10
end
报错如下:
Exception in thread "main" java.lang.NumberFormatException: For input string: " 1 "
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:638)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.main(Main.java:114)
根据测试样例的提示可以明白应该是空格没有去除导致出现了溢出产生的报错,所以使用trim方法去除空格。
第二次作业
答题程序二
输入样例4:
试卷题目的顺序与题号不一致。例如:
N:1 #Q:1+1= #A:2
N:2 #Q:2+2= #A:4
T:1 2-70 1-30
N:3 #Q:3+2= #A:5
S:1 #A:5 #A:22
end
输出答案发现两个题目分值是反的,应该输出2+2=5false1+1=22false0 00,实际输出了2+2=5false,1+1=22~false,0 0~0,然后我对程序逻辑进行了修改,让输出的匹配顺序不受输入影响。
测试样例七
乱序输入+分值不足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
S:1 #A:5 #A:4
end
跑完发现答案错误,看代码半天发现是习惯性把paper与“1”用+“ ”分隔了,还是得多看题目要求和样例输出。。
第三次作业
答题判题程序三
输入样例3:
N:1 #Q: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 #A:2-4
D:N-2
end
结果输出了20201103 Tom: 0~0
我测试时发现只要有两个题目输出的信息就会缺0,要么就是格式有问题,于是写了一个逻辑有点绕的判断程序输出最后一行的数据。
System.out.print(student.id + " " + student.name + ": " + totalScore);
int howmuch=0;//判断题目数量
int howmany=0;//判断题目数量
for(Integer value: questionScoresGet.values())
{
if(questionScoresGet.size()!=1&&howmuch0)
{
howmuch=1;
howmany=1;
System.out.print(" ");
}
if(howmuch1&&howmany0||howmuch0)
{
System.out.print("~");
}
howmany=0;
System.out.print(value);
}
}
总体如上,写了两个变量去控制可能的所有情况。
然后就可以正常输出20201103 Tom: 0 0~0了。
跑样例四的时候才发现错误格式输入的判断我还没写。(难怪老师让用正则表达式)
报错信息如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at ExamSystem.addQuestion(Main.java:52)
at Main.main(Main.java:191)
所以在分割写入信息前先写上正则表达式去判断,比如:
if (!input.matches("#T:\d+( \d+-\d+)+")) { // 正则表达式验证格式
System.out.println("wrong format:" + input);
return;
}
在各个add方法里加上类似的正则表达式判断就可以解决这个问题了。
(4)改进建议
由于第二次答题判题系统Main结构太臃肿,于是我将功能拆分重整,对第二次代码的结构进行了改进,于是我新写了一个类:ExamManager
它的主要属性有:ExamSystem examSystem:实例化的ExamSystem对象,用于管理所有的试题、试卷和学生信息。
Scanner scanner:用于从控制台读取用户输入。
Map<Integer, TestPaper> testPapers:管理试卷的集合,可以根据ID访问特定试卷。
Map<String, Student> students:管理学生的集合,可以根据学生ID访问特定学生。
主要功能就是将Main里的功能放入里面了:
添加问题:添加新问题。
添加试卷:可以创建新的试卷,并向其中添加问题。
添加学生:可以注册新学生。
评卷:处理学生的答案并计算分数。
显示成绩:输出每个学生的分数和答案。
然后代码整体的结构分工就更为明确了。
(5)总结
在这三次代码编写的过程中,我学到了很多,我初步理解了无参构造方法和有参构造方法的作用及差异,并学会了如何定义不同类型的构造方法来初始化对象。熟悉了几种基本的排序(如冒泡排序、选择排序)和查找(如线性查找、二分查找)算法,并能够实现这些算法解决实际问题。了解了封装、继承、多态等面向对象的核心特性,并尝试将这些理念应用到项目开发过程中去。掌握了一些常用的Java标准库中的工具类,例如String
, ArrayList
, HashMap
等容器类的操作以及日期时间处理等相关功能。
需要进一步改进的方向有深入理解设计模式目前对于一些常见的设计方式有了初步认识,但还需通过更多实例加深理解和灵活运用。以及异常处理机制,虽然学到了try-catch语句块的基本用法,但对于如何合理地抛出异常并妥善处理还有待提高。还有性能优化技巧学会分析程序运行效率,掌握一些基础的性能调优策略,比如使用更高效的算法或数据结构。对于正则表达式的应用也不太熟练。
希望辛苦老师能多写些测试点相关输入输出样例来帮助查找代码的漏洞,当然现在其实也够多了。