JAVA第二次博客作业
一、前言
总结三次题目集的知识点、题量、难度等情况
第四次作业 答题判题程序-4
在第三次题目的基础上,增加了对多选题和填空题的判断,这里要着重注意这两种题型的给分方式。多选题“答案包含所有正确答案且不含错误答案给满分;包含一个错误答案或完全没有答案给0分;包含部分正确答案且不含错误答案给一半分,如果一半分值为小数,按截尾规则只保留整数部分。”填空题“答案与标准答案内容完全匹配给满分,包含一个错误字符或完全没有答案给0分,包含部分正确答案且不含错误字符给一半分,如果一半分值为小数,按截尾规则只保留整数部分。”并且输出的时候,判断结果除了'true'、'false',增加了一项‘partially correct’表示部分正确。
主要知识点:
- 面向对象编程 使用继承(如
MultipleChoiceQuestion
继承自Question
)来表示题目的不同类型。 - 多态:在
AnswerSheet
类中根据题目类型(单选题、多选题、填空题)调用不同的计算得分和正确性的方法。 - 数组和集合操作:在
TestParser
类中动态扩展数组和列表以存储解析后的数据。
第五次作业 家居强电电路模拟程序-1
本题要求设计一个智能家居强电电路模拟系统,模拟开关、分档调速器、连续调速器对灯和风扇等受控设备的影响。系统需要处理设备连接信息、控制设备调节信息,并根据这些信息计算并输出各设备的状态或参数。输入以"end"结束,输出要求按设备类型和编号顺序显示所有设备的状态。
主要知识点:
- 面向对象编程(OOP):设计电路设备类、受控设备类、控制设备类等,以及它们的属性和方法。
- 类设计:设计类图,定义类之间的关系,包括继承和关联等。
- 数据结构:使用合适的数据结构(如List、Map...)来存储设备状态和连接信息。
第六次作业 家居强电电路模拟程序-2
本次迭代在“家居强电电路模拟程序-1”的基础上增加了对并联电路的处理,同时扩展了受控设备的种类,新增了落地扇,并且开始考虑设备电阻对电路的影响。
主要知识点:
- 类、继承、多态、封装
- 面向对象设计的八大原则
- 方法重载和覆盖
- 设备类型的转换
题量&难度:题目经过每次迭代,其代码量和难度都会逐渐增加,由几百行到上千行。
二、设计与分析
重点对题目的提交源码进行分析,可参考SourceMontor的生成报表内容以及PowerDesigner的相应类图,要有相应的解释和心得(做到有图有真相)。
第四次作业 答题判题程序-4
设计类图如下:
相比于答题判断程序-3,这次设计了更加复杂的类,增加了MultipleChoiceQuestion类,继承自Question
类,用来为多选题构造属性和方法。
在这个类图中:
-
Main类:负责初始化
TestParser
对象,解析输入,处理学生、试卷、题目和答卷信息,并输出结果。 -
TestParser类:负责解析输入的字符串,包括学生信息、试卷信息、题目信息和答卷信息,并验证格式。
-
Student类:表示学生,包含学号和姓名属性,以及获取和设置这些属性的方法。
-
Exam类:表示试卷,包含试卷号和题目列表,提供方法管理题目和计算总分。
-
Question类:表示题目,包含题目编号、内容、标准答案、分值和类型标志。
-
AnswerSheet类:表示答卷,包含试卷号、答案列表、得分列表和判断答案正确性的方法。
-
ExamSystem类:管理学生与其试卷和答卷的关系,提供方法添加和检索学生信息。
-
MultipleChoiceQuestion类:继承自
Question
类,表示多选题,提供计算得分和正确性的方法。
主要逻辑和详细设计:
1.输入解析:
TestParser
类中的parseInput
方法读取输入直到"end"结束,然后再根据输入类(如"#N"、"#T"等)存储到各自的StringBuilder中,然后用正则表达式验证格式是否正确。
2.学生信息处理:
parseStudent
方法解析学生信息,创建Student
对象并存储。
3.题目信息处理:
parseQuestions_N
、parseQuestions_Z
和parseQuestions_K
方法分别解析普通题、选择题和填空题,创建Question
对象并存储。
针对于填空题的文本解析,由于填空题的答案可能不唯一,答案之间用“或”分隔,方法会将这些答案分割并添加到Question
对象的标准答案列表中;对于多选题的答案,则是直接添加到Question
对象的标准答案列表中。最后将对象添加到questions
数组中。
这里还创建了一个标识符flag用来区分题目类型,0为普通题目或单选题,1为多选题,2为填空题。
(但是后来当我对继承更加熟悉,再回过头看我的程序时,我觉得这一部分应该改成一个基础题目父类,然后派生出普通题、多选题、填空题三个子类,而不是直接设置一个标识符来区分!!!)
4.试卷信息处理:
parseExams
方法解析试卷信息,创建Exam
对象,并根据试卷信息添加题目。
5.答卷信息处理:
parseAnswerSheets
方法解析答卷信息,创建AnswerSheet
对象,并与对应的Exam
对象关联。
6.得分和正确性计算:
AnswerSheet
类的CalAndJudge
方法对每个题目根据其类型(单选、多选、填空)分别调用不同的方法来计算得分和判断答案的正确性。如果题目有效(即不是“none_null”或“del_null”),则根据题目的类型标志(flag),选择对应的方法(`cal_0`、`cal_1`、`cal_2`)进行处理。如果学生没有对应的答案,则该题目得分为0,正确性为false。最后,计算总分。
cal_0方法是判断单选题的答案是否正确。如果学生的答案与标准答案完全一致,则该题目得分为题目的分值,正确性为true;否则得分为0,正确性为false。然后将得分累加到总分中。
cal_1方法计算多选题的得分和正确性。如果学生的答案包含所有正确答案且没有错误答案,则得满分;如果学生的答案包含部分正确答案但不含错误答案,则得一半分;如果学生的答案包含错误答案或没有答案,则得0分。
public void cal_1(Question q, int i) { List<String> standardAnswers = q.getStandardAnswers(); List<String> studentAnswers = answers.get(i); // 包含所有正确答案且不含错误答案给满分 if (studentAnswers.containsAll(standardAnswers) && standardAnswers.containsAll(studentAnswers)) { answersScore.add(q.getScore()); answersBoolean.add("true"); answerRight.add(2); // 完全正确 } // 包含部分正确答案且不含错误答案给一半分 else if (!studentAnswers.containsAll(standardAnswers) && standardAnswers.containsAll(studentAnswers)) { int halfScore = q.getScore() / 2; answersScore.add(halfScore); answersBoolean.add("partially correct"); answerRight.add(1); // 部分正确 } // 包含错误答案或没有答案给0分 else { answersScore.add(0); answersBoolean.add("false"); answerRight.add(0); // 错误 } totalScore += answersScore.get(i); }
cal_2方法计算填空题的得分和正确性。如果学生的答案与标准答案完全一致,则得满分;如果学生的答案包含部分正确答案且没有错误字符,则得一半分;如果学生的答案包含错误字符或没有答案,则得0分。
public void cal_2(Question q, int i) { List<String> standardAnswers = q.getStandardAnswers(); List<String> studentAnswers = answers.get(i); String studentAnswer_str = answers.get(i).stream().collect(Collectors.joining("")); // 将学生答案列表转换为字符串 String hole_answer = getStandardAnswersJoined(standardAnswers); //System.out.println("完整的填空题:"+hole_answer); // 答案与标准答案内容完全匹配给满分 if (studentAnswer_str.equals(hole_answer) ) { answersScore.add(q.getScore()); answersBoolean.add("true"); answerRight.add(2); // 完全正确 } // 包含部分正确答案且不含错误字符给一半分 else if (studentAnswers.size() > 0 && !studentAnswers.containsAll(standardAnswers) && standardAnswers.containsAll(studentAnswers)) { int halfScore = q.getScore() / 2; answersScore.add(halfScore); answersBoolean.add("partially correct"); answerRight.add(1); // 部分正确 } // 包含错误字符或完全没有答案给0分 else { answersScore.add(0); answersBoolean.add("false"); answerRight.add(0); // 错误 } totalScore += answersScore.get(i); }
7.输出结果:
在main
方法中,遍历学生ID,对于每个学生,输出其所有试卷的答题信息和得分。
第五次作业 家居强电电路模拟程序-1
设计类图如下:
这次类图设计主要参考了老师在题目中给到的类图。
在这个类图中:
- TestParser类负责解析输入、管理整个流程
- SeriesCircuit类管理所有设备和连接关系
- CircuitDevice类设备基类,定义共同属性
- ControlDevice类是控制设备基类,派生三个子类:开关、分档调速器、连续调速器
- ControlledDevice类时受控设备基类,派生出两个子类:灯、风扇
主要逻辑和详细设计:
以上是整个代码的顺序图,程序主要分为四个阶段,分别是初始化阶段、解析和创建设备阶段、处理控制命令阶段、计算和输出结果阶段。
1.初始化阶段
程序通过main方法接收用户输入,并创建TestParser实例来解析输入和管理流程。TestParser实例进一步创建SeriesCircuit实例,负责管理所有设备和连接关系。
2.解析和创建设备阶段
接着通过runParsing(input)函数开始解析输入的信息,由其中的parseinput()将输入按照类型分类,parseConnections() 解析连接信息,解析出来的设备再通过createDevice(typeId) 创建具体设备实例,创建一个new CircuitDevice,它是所有设备的基类。
创建完设设备后,再通过addConnection()存储设备间的连接关系。
3.处理控制命令阶段
parseK/F/L方法解析控制命令,然后通过findTypeId()找到对应设备,更新设备状态,如开关切换、调速器调节等。
4.计算和输出结果阶段
通过calculateVoltageDifferences函数计算电路中设备电压差,首先设定初始电压值为220V,遍历设备列表devices。在遍历过程中,对于每个控制设备,通过类型转换获取其实例并计算输出电压,若输出电压为0,则所有设备电压差均为0,对于受控设备,通过类型转换获取其实例并计算其电压差,即当前电压值与前一个设备输出电压的差值,并更新受控设备的电压差。这样,依次处理所有设备后,可以得到整个电路中所有受控设备的电压差。
// 将设备转换为控制设备 public ControlDevice castToControlDevice(CircuitDevice device) { if (device instanceof Device_K) { return (Device_K) device; // 转换为 Device_K } else if (device instanceof Device_F) { return (Device_F) device; // 转换为 Device_F } else if (device instanceof Device_L) { return (Device_L) device; // 转换为 Device_L } else { throw new ClassCastException("设备无法转换为控制设备类型"); } } //将设备转换成受控设备代码 也是如此
// 获取所有设备并排序 public List<CircuitDevice> getDevices() { // 定义设备类型的顺序 List<CircuitDevice> tempDevice = new ArrayList<>(seriesCircuit.getDevices()); final List<String> typeOrder = Arrays.asList("K", "F", "L", "B", "R", "D"); // 排序设备 tempDevice.sort((d1, d2) -> { int typeComparison = Integer.compare(typeOrder.indexOf(d1.getType()), typeOrder.indexOf(d2.getType())); if (typeComparison == 0) { return Integer.compare(d1.getId(), d2.getId()); // 同一类型下按 ID 排序 } return typeComparison; // 按类型排序 }); return tempDevice; // 返回排序后的设备列表 }
最后输出所有设备的状态或参数。
第六次作业 家居强电电路模拟-2
设计类图如下:
和上一次相比,增加了Circuit类、ParallelCircuit类和MajorCircuit类来管理电路。
在这个类图中:
-
TestParser类:负责整体的输入解析和结果输出,协调各个部分的工作。
-
Circuit类(抽象):表示电路,包括设备存储和连接信息,提供创建设备和添加连接的方法。
-
CircuitDevice类(抽象):表示电路设备,定义了设备的基本属性和方法。
-
ControlDevice类(抽象):表示控制设备,继承自
CircuitDevice
,提供控制设备的特有方法。 -
ControlledDevice类(抽象):表示受控设备,继承自
CircuitDevice
,提供受控设备的特有方法。 -
Device_K、Device_F、Device_L:分别表示开关、分档调速器和连续调速器,实现具体的控制逻辑。
-
Device_B、Device_R、Device_D、Device_A:分别表示白炽灯、日光灯、吊扇和落地扇,实现具体的受控逻辑。
-
SeriesCircuit类:表示串联电路,继承自
Circuit
,管理串联电路的设备和连接。 -
ParallelCircuit类:表示并联电路,继承自
Circuit
,管理并联电路的串联电路连接。 -
MajorCircuit类:表示主电路,继承自
Circuit
,管理整个电路的主路径。
主要逻辑和详细设计:
(这里其实和上一个大作业没太大区别,就是添加了并联电路,然后多了一些逻辑处理)
1.初始化阶段
程序通过main方法接收用户输入,并创建TestParser实例来解析输入和管理流程。TestParser实例进一步创建SeriesCircuit实例,负责管理所有设备和连接关系。
2.解析和创建设备阶段
TestParser类读取输入,根据输入类型分类并存储。电路部分以‘#T’开头的为串联电路,其中‘VCC’开头的成为主电路,‘IN’开头的为支路,‘#M’开头的为并联电路,然后各自添加到相应的电路中。 接着开始解析连接信息,创建设备实例,添加连接信息。 这里在TestParser类里创建如下数据结构存储三种电路信息。
private List<SeriesCircuit> seriesCircuits; // 支路 private List<ParallelCircuit> parallelCircuits; // 并联 private MajorCircuit majorCircuit;//电路主路
和上一题存储设备和连接情况不同的是,我使用了Map、List来存储主路和每一条支路的设备和连接信息;
protected Map<String, CircuitDevice> devices;//存放设备 protected List<String>connections;//存放连接信息
而并联电路中,使用的是一个存储串联电路对象的列表。
protected List<SeriesCircuit>connections;
每种电路都可以看成是一个独立的电路设备,拥有输入电压、输出电压、电阻、电流这些基本属性。
3.处理控制命令阶段
parseK/F/L方法解析控制命令,然后通过devices.get(id)找到对应设备,更新设备状态,如开关切换、调速器调节等。
4.计算和输出结果阶段
这里计算电压差就比较复杂了,因为加入了并联和电阻的条件,而且还要考虑电路中的各种情况,因此我设置了一个标志位 flag ,如:0-断路、1-短路、2-通路。
先遍历各个电路,计算它们整体的电阻,并判断电路连通情况以及设置对应的标志位。
对于并联电路,如果它其中的支路(串联电路)全部断路,则该并联电路断路;如果有一条路短路,则该并联电路短路,并且短路情况要考虑是否有控制设备,一条支路上全为控制设备且输出电压不为0,也算是短路情况。
//计算并联电路上的电阻 //如果短路 也就是没有设备 整个并联电路电阻为0 public void calPartCircuitResistance(){ double resistance_partcircuit=0; double temp_vol=0; int allflag=0; for(SeriesCircuit temp:connections){ if(temp.flag==1){//并联电路里 有一个短路就都短路 flag=1; return; } if(temp.flag==2) {//连通计算电阻 if (temp.resistance != 0) temp_vol += 1 / temp.resistance; } allflag+=temp.flag; } if(allflag==0){ flag=0;//如果并联中的电路全部断开,则并联 设为断开 } if(temp_vol!=0) resistance_partcircuit=1/temp_vol; resistance = resistance_partcircuit;//更新电阻 } }
计算完各电路电阻之后,如果整个电路是连通的,就开始计算各部分电流,然后求它们的电压差,先求整体(并联部分和主支路),再求细节(各电路上的设备)的电压差。
最后对设备排序之后就可以输出结果了。
三、踩坑心得
对源码的提交过程中出现的问题及心得进行总结,务必做到详实,拿数据、源码及测试结果说话,切忌假大空
针对第六次大作业:
一开始编程的时候,电路中的好多情况都没有想到,导致一大堆测试点没过。
这就是不思考就上手写代码的效果:)
然后自己编写了一些测试样例也测得没问题,还是想不出来问题在哪,就去和同学交流,借助他们的样例进行测试。
最后发现是我没考虑到 VCC直连并联电路 以及 电路出现短路的情况。
并联电路中,在不是断路的前提下,支路没有设备是短路,只有控制设备没有受控设备也是短路;但是主路如果没有设备或者只有控制设备时,不能直接判定为短路,还要考虑到其中并联的状态。
而且我一开始写的时候,就是简单的把断路电路的电阻设置为0,这样在我添加判断是否为短路的代码就比较复杂和困难,然后我就删除了这个逻辑,加了一个标志位 flag,去设置其短路、断路、通路的状态,整个过程还是比较复杂的,就是因为我敲代码之前没规划好。
设置了标志位之后,在计算电阻、电流、电压差时都对其进行判断。
修改了这一部分逻辑之后,就通过了大部分测试点。
但最后还是有三个测试点没过,不知道为啥,可能又漏掉了一些细节吧。
四、改进建议
对相应题目的编码改进给出自己的见解,做到可持续改进
第四次大作业的类设计,可以设置成一个基础题目父类,然后派生出普通题、多选题、填空题三个子类,而不是直接设置一个标识符来区分是什么类型的题目,当初对继承不是很了解,现在熟悉了之后,才知道继承的重要和方便。
写代码之前,一定一定要先多读几遍题目内容,起码对题目框架有个清晰的认识,然后也可以把思路和考虑到的各种情况都记录下来,多读几遍题目,边读边补充遗漏的、没考虑到的情况。
有的类实现的功能太多了,维护和修改起来不是很方便,下次可以在实现多个功能的类里面设计内部类。
五、总结
对本阶段三次题目集的综合性总结,学到了什么,哪些地方需要进一步学习及研究。
通过这三次大作业的编写,我对面向对象编程的核心概念,包括继承、多态和封装有了更清晰的认识,也真正意识到了它们的重要和方便,这些是只学理论所不能体会到的,只有自己真正实践运用起来,才能体会到那些理论知识的意义和重要性。
还有,测试也很重要,一个好的测试样例可以帮你找到程序中的错误和不足,我也应该学会编写不同情况下的测试样例,虽然这几次我都是在向同学询问测试样例。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)