(1)前言
这三次作业算是一个渐进和转折的过程,题目渐渐减少,主要还是要写迭代题,对我来说5-6难度还是蛮大的。4是让我学会了不少更新功能的思路和调试的方法。5-6则让我明白了代码结构的重要性。题目内容主要涉及类的设计等等,以及主要的答题判题程序还有电路系统的编写与迭代。在这三次作业里也算是被拷打了一番罢。
(2)设计与分析
第四次作业
答题判题程序四
这题在之前的基础上加上了对多选题的分析和设计,所以要改的逻辑其实不多,主要是要给多选题设计一套新的判断方法。代码由四个主要部分组成:
Question: 这个基础类封装了一个问答的基本属性,包含了题号 id、题目内容 question 和标准答案 answer。
MultipleChoiceQuestion 和 FillInTheBlankQuestion: 这两个类分别用于表示选择题和填空题,继承自 Question 类。这样做的好处是可以为不同类型的题目引入特有的逻辑,如果今后需要扩展题目类型(如判断题等),可以方便地在此基础上进行扩展。
TestPaper: 这个类表示一张试卷,包括试卷的ID以及题目得分的映射关系。它提供的方法包括添加题目以及计算试卷的总分。
Student: 这个类表示学生的基本信息,包括学号和姓名。
逻辑关系也可以延续前面设计的思路:
数据输入: 主程序循环读取数据,依次判断输入的格式,并调用相应的处理方法进行缓存。
格式验证: 在每个输入处理中,都有格式验证,确保输入符合预定义的条件。如果不符合,输出错误信息。
评分逻辑:对于每一个答卷,系统检查每道题的答案,判定结果,并计算得分。
支持删除题目的功能,删除后题目将以“无效”标识,并算作0分。
但是要注意的是:对于选择题,评分逻辑更复杂,需要考虑多种情况(如完全正确、部分正确、完全错误)。
结果输出: 所有信息都在评分处理完成后统一输出,包括成绩和提示信息。
具体的第一步设计如下:
Question 类
属性:
int id: 题号,用于唯一标识每道题。
String question: 题目内容,即学生需要回答的内容。
String answer: 标准答案,用于后续的评分判断。
方法:
Question(int id, String question, String answer): 构造方法,用于初始化题目的 id、question 和 answer。
MultipleChoiceQuestion 类
继承自: Question
方法:
由于没有新增属性或方法,直接使用 Question 类中的所有属性和方法。
FillInTheBlankQuestion 类
继承: Question
方法:
由于没有新增属性或方法,直接使用 Question 类中的所有属性和方法。
4. TestPaper 类
属性:
int id: 试卷编号,唯一标识一张试卷。
Map<Integer, Integer> questionScores: 存储题目的得分,key 是题目 id,value 是得分。
方法:
TestPaper(int id): 构造方法,初始化试卷 id。
void addQuestion(int questionId, int score): 添加题目到试卷的题目分值映射。
int getFullScore(): 计算该试卷的总分,并返回这个总分。
5. Student 类
属性:
String id: 学号,用于唯一标识每个学生。
String name: 学生姓名。
方法:
Student(String id, String name): 构造方法,初始化学生的 id 和 name。
6. ExamSystem 类
属性:
Map<Integer, Question> questions: 存储所有题目,key 是题目编号,value 是相应的题目对象(包括选择题和填空题)。
Map<Integer, TestPaper> testPapers: 存储所有试卷,key 是试卷编号,value 是相应的试卷对象。
Map<String, Student> students: 存储所有学生,key 是学号,value 是相应的学生对象。
Map<Integer, Integer> questionScoresGet: 存储每道题的得分,key 是题目 id,value 是得分。
Set
Map<Integer, Map<String, Map<Integer, String>>> answersCache: 缓存每个试卷的答案,key 是试卷 id,value 是一个映射,映射的 key 是学生的学号,value 是另一个映射,这个映射的 key 是题目的顺序,value 是学生的答案。
方法:
void addQuestion(String input): 添加填空题并检查输入格式。
void addTestPaper(String input): 添加试卷并检查输入格式,同时计算和输出总分警示(如果总分不等于 100)。
void addStudent(String input): 添加学生并检查输入格式。
void addAnswer(String input): 添加学生的答案并检查输入格式,缓存每个学生的答案。
void deleteQuestion(String input): 删除题目信息。
void gradeAllAnswers(): 批量评分,遍历所有的答卷,依据缓存的答案和题目信息进行评分。
void gradeAnswers(TestPaper testPaper, Student student, Map<Integer, String> answers): 对每一张试卷进行评分,包括各题判断和总分计算逻辑。
void addMultipleChoiceQuestion(String input): 添加选择题。
void addFillInTheBlankQuestion(String input): 添加填空题。
功能流程分析
首先代码还是要对输入进行处理:
程序在启动时进入一个while循环,nextLine持续等待用户输入。用户输入通过标准输入读取一行,直到输入“end”标志结束输入。
然后就是识别输入类型:
程序根据输入的内容前缀(#N:、#Z:、#K:、#T:、#X:、#S:、#D:)来用一排if判断输入数据的类型,并调用对应的方法来处理。
其它输入就输出格式错误信息。
进入对应的add方法之后就是在每个处理方法中,用正则表达式对输入的格式进行验证。如果格式不符,则输出wrong format: {input} 的错误信息,不会添加任何内容。
然后判断完在格式正确的情况下,系统会将数据存储到相应的集合中:
题目信息存储在 questions 映射中。
试卷信息存储在 testPapers 中。
学生信息存储在 students 中。
学生答案缓存到 answersCache 中。
然后检查总分警示:
在添加试卷后会立即检查该试卷的总分是否等于 100。如果不等于 100,打印警告信息,如 alert: full score of test paper {试卷号} is not 100 points。
当用户输入“end”后,程序将跳出输入循环,开始评判所有的答案。
之后就进入评分:
评分过程调用 gradeAllAnswers 方法,开始批量评分:
首先遍历储存的试卷和学生答案。然后对于每个试卷和学生,调用 gradeAnswers 方法进行评分处理。
评分的过程如下:
检查题目的有效性(存在性和删除状态)。
根据学生的答案与标准答案进行比较,确定每道题的得分。
支持选择题的部分正确评分。
输出每道题的内容、学生答案及判定结果(true / false / partially correct)。
最后输出每个学生的得分情况,格式为 学号 姓名: {题目得分} ~ {总分}。
如果有题目被删除或不存在,将用特定消息替代,例如 the question {题目 ID} invalid~0 或 non-existent question~0。
如上基本就能实现要求了。
第五次作业
家居强电电路模拟程序-1
这个是家居强电电路模拟程序的第一次迭代,理论上难度不会很大,但是我还是写得汗流浃背。
首先这题一开始还是应该先从输入的处理开始分析,可能根据不同输入放入不同存储中,然后去判断它的电路情况去分设备对应输出。总体实现功能需要模拟智能家居中的控制和受控设备。
所以核心设计大概要包括对两大设备的区分:
控制设备形式:
开关:具有两种状态(开/关),能够根据输入电位控制输出电位。
分档调速器:支持不同档位的调速(4档),其输出电位根据不同档位调节输入电压的不同比例。
连续调速器:能够按比例调节输出电位,范围在0.00到1.00之间,直接乘以输入电压。
受控设备形式:
白炽灯:根据电压差显示不同的亮度(0-200lux),电压差小于某一阈值则关闭。
日光灯:只有亮和灭两种状态(亮度180lux)。
风扇:根据输入电压的差值调节转速(80-360转/分钟),低于某个电压则停止运行。
控制命令:通过特定的指令格式修改设备状态或参数。
所以我们要模拟不同设备的状态,并根据输入条件动态更新。
然后实现设备之间的连接关系和相互作用也是个难点,对我来说也是难死我了。
综上我就有了一个大概的类设计思路:
设备抽象类和子类:
创建一个设备抽象类,定义所有设备的公共接口,比如输入、输出及状态获取等。
针对不同类型设备(开关、调速器、灯、风扇),实现各自的具体逻辑。
电路管理类:
负责设备的创建、连接关系的维护以及状态的更新和输出。
可以在此类中实现输入命令的解析与处理。
然后再思考一下整体逻辑的处理应该是怎样的,根据题目运行该系统的大致步骤如下:
读取输入:包括设备连接信息,控制命令等。
创建设备实例:根据连接信息,实例化相应的设备对象,并建立直接的连接关系。
处理控制命令:解析用户输入的控制指令,更新开关状态和调速器档位。
更新设备状态:根据当前输入的电压和连接状态,计算受控设备的亮度和速度。
输出结果:按照指定格式输出各设备的状态信息,确保输出的准确性和格式的正确。
然后我根据这个去写流程和方法。最后决定还是使用MAP来存储数据,在这里可以存设备还有电路还有编号之类比较适用。同时对于引脚这种目前好像没有什么用的数据也先存一下,把它当成设备类的属性存一下。后续有用方便修改。
同时要逐行读取输入数据,判断数据类型(各种设备和VCC,GND)进行分类存储,所以还是用while (sc.hasNextLine())判断去读输入。
最后,通过简单的循环输出results中所有警告信息和结果信息。
然后以下就是我初步得出的类设计方法:
基类:Device
属性:
protected String id:设备的唯一标识符。
protected double inputVoltage:输入电压,默认为0。
protected double outputVoltage:输出电压,默认为0。
方法:
public abstract void updateOutput():抽象方法,子类根据自身逻辑实现输出电压的更新。
public String getId():获取设备标识符。
public double getInputVoltage():获取输入电压。
public double getOutputVoltage():获取输出电压。
public abstract String getStatus():抽象方法,子类根据自身逻辑实现状态的返回。
@Override public String toString():返回设备的基本信息格式。
开关类:Switch
继承自Device。
属性:
private boolean state:开关状态,false表示打开 (turned on),true表示关闭 (closed)。
方法:
public void toggle():切换开关的状态。
public String getStatus():返回开关的当前状态,使用"closed" 或 "turned on"。
@Override public void updateOutput():更新输出电压,若开关关闭则输出电压为0,否则为220。
@Override public String toString():返回开关的详细信息,包括状态和输出电压。
分档调速器类:StepRegulator
继承自Device。
属性:
private int step:当前档位,范围从0到3。
方法:
public String getStatus():返回当前档位。
@Override public void updateOutput():根据输入电压更新输出电压,假设目前简化为直接输出输入电压。
@Override public String toString():返回分档调速器的详细信息。
连续调速器类:ContinuousRegulator
继承自Device。
属性:
private double ratio:用于调节输出电压的比例,范围在0.0和1.0之间。
方法:
public void setRatio(double ratio):设置输出比例,确保在合法范围内(0到1)。
public String getStatus():返回调速器的输出比例,保留两位小数。
@Override public void updateOutput():根据输入电压和比例计算输出电压。
@Override public String toString():返回连续调速器的详细信息。
白炽灯类:IncandescentLamp
继承自Device。
属性:
private double brightness:当前亮度。
方法:
public String getStatus():返回当前的亮度,格式为整数。
@Override public void updateOutput():根据输入电压计算亮度,使用线性关系。
@Override public String toString():返回白炽灯的详细信息。
日光灯类:FluorescentLamp
继承:继承自Device。
属性:
private double brightness:当前亮度。
方法:
public String getStatus():返回当前亮度,固定为180或0。
@Override public void updateOutput():根据输入电压设置亮度。
@Override public String toString():返回日光灯的详细信息。
风扇类:Fan
继承:继承自Device。
属性:
private double speed:当前转速。
方法:
public String getStatus():返回当前的转速。
@Override public void updateOutput():根据输入电压计算转速。
@Override public String toString():返回风扇的详细信息。
8. 智能家居系统类:SmartHomeSystem
属性:
private List
private Map<String, List
private Device vccDevice:表示电源设备,固定电压220V。
方法:
public SmartHomeSystem():构造方法,初始化VCC设备并将其加入设备列表。
public void addDevice(Device device):添加设备到设备列表。
public boolean hasDevice(String deviceId):检查设备是否存在。
public void connect(String from, String to):建立设备之间的连接。
public void setVCC():设置VCC电压并更新直接连接设备的输入电压。
private void propagateVoltage(String deviceId, double voltage):递归更新连接设备的输入电压。
public void setInputVoltage(String deviceId, double voltage):设置设备的输入电压并更新其输出。
public void toggleSwitch(String switchId):切换开关状态并更新连接设备的电压。
private void updateConnectedDevices(String switchId):更新与开关连接的设备的电压。
public void updateOutputs():更新所有设备的输出。
public void printStatus():打印所有设备的状态,按照指定格式输出。
9. 主类:Main
方法:
public static void main(String[] args):程序的入口,负责接收输入、初始化系统、处理连接信息和控制命令,并输出最终结果。
类之间的关系
所有设备类(如Switch、StepRegulator、ContinuousRegulator、IncandescentLamp、FluorescentLamp、Fan)都继承自Device类,形成了一种“是一个”关系。
SmartHomeSystem类管理所有设备,并且通过Map存储设备之间的“连接”关系。它也负责更新状态和传播电压。
主类Main负责创建SmartHomeSystem实例,并处理用户输入,通过SmartHomeSystem提供的方法来控制设备。
基本设计大概就是这样,实际设计还是要把关系和方法调节好。
第六次作业
家居强电电路模拟程序-2
家居强电电路模拟程序-2是在1的基础上的迭代和优化,但是我家居强电电路模拟程序-1的电压传递逻辑没有写出来,所以痛定思痛决定重新设计来写2。然后我换了一下写的思路,首先还是先把各个设备的类写好,还有把输入输出的逻辑写好,把框架搭好后再去思考如何存数据,然后我打算把设备信息存入一条条电路中,同时电路能作为设备被电路存入,这样就可以实现信息存入,然后最后把重心全放在我第一次实验没有解决的电压传递问题上。
所以我重新设计了一下代码的结构,
抽象类 Device
属性:
id: 设备ID
pin: 引脚号
inputVoltage: 输入电压
outputVoltage: 输出电压
resistance: 电阻值
方法:
getId(), getPin(), getInputVoltage(), getOutputVoltage(), getResistance(): 获取相关属性的方法
isSwitch(): 判断设备是否为开关
updateOutputVoltage(): 抽象方法,更新设备的输出电压,具体实现由子类实现
设备类(多种类型设备)
开关类 Switch:
属性:
status: 状态(0=off, 1=on)
方法:
updateOutputVoltage(): 更新输出电压
toggle(): 切换开关状态
getStatusString(): 获取开关状态字符串
isOn(): 判断开关是否打开
分档调速器 DimmingSwitch 和 连续调速器 ContinuousDimmingSwitch:
这两个类都是根据输入电压调整输出电压。
DimmingSwitch 类有 gear 属性表示档位,ContinuousDimmingSwitch 类则有 position 属性表示位置(范围0到1)。
灯具类 IncandescentLamp 和 FluorescentLamp:
这两个类代表不同类型的灯具,分别有各自的亮度计算方法。
风扇类 CeilingFan 和 FloorFan:
这两个类用于表示不同类型的风扇,具有计算转速的方法。
然后就是最重要的新增类:
基础电路类 Circuit
属性:
devices: 包含的设备列表
totalResistance: 电路的总电阻
inputVoltage: 电路的输入电压
ifMainCircuit: 用于标识是否为主电路
方法:
addDevice(): 添加设备
updateTotalResistance(): 更新总电阻的计算逻辑
updateOutputVoltages(): 更新每个设备的输出电压
各种获取器方法,如 getInputVoltage()、getTotalResistance()。
电路子类
串联电路 SeriesCircuit:
延续父类 getTotalResistance() 方法,对串联电路设备电阻求和。
并联电路 ParallelCircuit:
同样继承 Circuit,重写 getTotalResistance() 方法,能够计算并联电路的电阻。
处理输入电压的设置逻辑。
设备电路类 CircuitDevice
属性:
circuit: 一个包含的电路
方法:
updateOutputVoltage(): 更新电路的输出电压。
电路模拟器 CircuitSimulator
属性:
deviceMap: 存储设备的映射
circuitMap: 存储电路的映射
scanner: 用于输入的扫描器
方法:
processInput(): 处理输入命令
handleSeriesCircuit() 和 handleParallelCircuit(): 分别处理串联和并联电路的输入
handleDeviceControl(): 控制设备的状态
startInputLoop(): 启动输入循环
printDeviceStatus(): 打印设备状态的方法
主程序 Main
方法:
main(): 程序入口,实例化 CircuitSimulator 并启动输入循环。
(3)踩坑心得
第四次作业
答题判题程序四
多空格:
多选题是一定含有空格符的。例如输入:
N:1 #Q:1+1= #A:2
Z:2 #Q:党十八大报告提出要加强()建设。A 政务诚信 B 商务诚信 C社会诚信 D司法公信 #A:A B C D
T:1 1-5 2-9
X:20201103 Tom
S:1 20201103 #A:1-5 #A:2-A C
end
报错如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at ExamSystem.addMultipleChoiceQuestion(Main.java:224)
at Main.main(Main.java:253)
根据以前的溢出错误思路去检查了多选题的设计思路,果然还是是空格没有去除导致出现了溢出产生的报错,所以使用trim方法去除空格。
多选题判分分数输出有误:
输入:
N:1 #Q:1+1= #A:2
Z:2 #Q:党十八大报告提出要加强()建设。A 政务诚信 B 商务诚信 C社会诚信 D司法公信 #A:A B C D
T:1 1-5 2-9
X:20201103 Tom
S:1 20201103 #A:1-5 #A:2-A C
end
实际输出里判分:
党十八大报告提出要加强()建设。A 政务诚信 B 商务诚信 C社会诚信 D司法公信 ~A C~false
20201103 Tom: 0 0~0
应该输出:
党十八大报告提出要加强()建设。A 政务诚信 B 商务诚信 C社会诚信 D司法公信~A C~partially correct
20201103 Tom: 0 4~4
所以明显是多选的判分方法设计有问题,具体就是改进判断包含的逻辑:
所以重写了多选的判分方法:
String[] correctAnswers = correctAnswer.split(" ");
String[] studentAnswers = studentAnswer.split(" ");
int correctCount = 0;
for (String sa : studentAnswers) {
if (Arrays.asList(correctAnswers).contains(sa)) {
correctCount++;
}
}
int partialScore = 0;
if (correctCount == correctAnswers.length) {
partialScore = score;
} else if (correctCount > 0) {
partialScore = score / 2;
}
System.out.println(questions.get(questionId).question + "~" + studentAnswer + "~" + (partialScore == score ? "true" : (partialScore == 0 ? "false" : "partially correct")));
totalScore += partialScore;
questionScoresGet.put(questionId, partialScore);
}
第五次作业
家居强电电路模拟程序-1
输出顺序不符合要求:
给输出的各个设备设定了输出的优先级解决。
第六次作业
家居强电电路模拟程序-2
电压传递有问题:
在实现我的设计思路的第一步和第二步的时候,又回到了电压传递的问题上,首先面临的问题是一开始输入电压,按我的设计一定是先在主电路更新才能给其他的电路传上电压,所以得先给主电路输电压,同时在后面给其他电路更新电压,所以我把原来的for循环扩充为两个,第一个特判进入主电路:
for (Circuit circuit : circuitMap.values()) {
((ParallelCircuit)circuit).setInputVoltage1(ParallelCircuitVoltage);
}
}
for (Circuit circuit2 : circuitMap.values())
{
boolean switchIsOn = false;
for (Device device : circuit2.getDevices()) {
}
if(circuit2 instanceof SeriesCircuit){
if(circuit2.getIfMainCircuit()){
j=1;
List
......
}
circuit2.updateOutputVoltages();
}
else{
((ParallelCircuit)circuit2).setInputVoltage1(ParallelCircuitVoltage);
}
}
这样就可以处理电压的初步传递了。
然后是电阻的处理有问题,主要是电路的电阻作为设备类和电路类时的同步更新问题。
然后我重写了电路设备类的属性,同时在电路类里增加获得电阻的方法。
public CircuitDevice(String id, Circuit circuit) {
super(id, -1); // -1表示这是一个电路设备,而不是具体引脚的设备
this.circuit = circuit;
this.resistance = circuit.getTotalResistance();
}
这样电路作为设备就能获得作为电路的总电阻去分配电压了。
(4)改进建议
主要还是家居强电电路模拟程序的设计还时有很多需要改进的地方,第一次设计的时候是用流作为主要的逻辑处理,但是用得不是很好,没有足够的熟练度去处理判断逻辑和追踪数值传递的过程,所以电压传递的逻辑改起来比较困难,因为不能高效跟踪判断的正确与否和电压传递的停留w位置。
家居强电电路模拟程序-2的设计虽然重新设计了代码结构,同时也基本实现了自己预想的三个步骤,但是设计的线性思路太明显了,而且在对题目的电路电阻的计算没有考虑到开关开闭对并联电路电阻的影响,导致整个电阻的逻辑判断少了很重要的一部分,应该先确定整个电路的所有状态再去更新电阻。还有电路作为电路类和设备类的设计有比较大的问题,两种类的耦合性强但是设计时思路里的分工并不明确,这也导致了后续修改更新逻辑时比较费力,不知道应该先从哪把数据拿出。
(5)总结
在这三次代码编写的过程中,我学到了很多,尤其是5-6次作业,我从中深刻意识到了设计的重要性,电路一的设计不合理让我更新电压时难以追踪运行的过程,电路二的设计虽然有所优化,但是设计时没有把整个问题考虑清楚,导致完成框架后400多行代码扩到900多,同时代码量的提升导致设计的困难加大,冗余度提高了,同时没有很好地遵守单一职责,而是根据个人的短期思路去直接增加功能,导致代码越写越多越复杂,同时修改难度也不断加大,导致花费了大量时间去修改代码也无法完成所有基本的功能实现,尽管在周一到周五写出了基本框架,但是整个周末的时间也难以完成对功能逻辑的修改。
所以还是得去深入理解设计模式,学会设计的思路和方法还有性能优化技巧学会分析程序运行效率,掌握一些基础的性能调优策略,比如使用更高效的算法或数据结构。还有写代码时不能只考虑眼前的实现,还要考虑增加代码对整体设计的影响,尽量用方法去实现功能,而不是打补丁一样去做补救式的代码增加。