-
前言
经过这几次JAVA练习题的训练,我对JAVA编程语言有了更深入的理解,同时在程序开发设计环节也有了很大的进步,吸收了上次编程练习的教训后,我在本阶段的编程练习中也有针对性的进行了改进。
本次的JAVA练习内容丰富,包括各种数据结构的应用,如哈希表,列表,数组等;迭代器的使用;创建有序集合;类关系的应用,如继承关系以及抽象类和抽象函数的复写问题等。而主要难点在于这几次练习的逻辑设计和代码运行流程上。
这次的三个JAVA练习难度上依旧是由浅入深,难的部分主要集中在答题判题程序和两次电路迭代程序,本次我主要针对这三个问题详细阐述我在编程中的设计和遇到的问题。 -
设计与分析
2.1 答题判题程序
在答题判题程序中,由于已经有了前几次的经验,只需要在先前的基础上对程序逻辑和运行进行修改和调试即可。本次判题程序新增的内容是对题目进行了改进,将题目分为选择题,多选题,填空题等。因此在判定的时候也有了更高的要求,多选题、填空题有新增部分正确的判定,同时,作答和标准答案的匹配也要更新,在分数计算上也要新增规则,部分正确得一半分;还要考虑多个同学有多张不同试卷的答卷的情况,需要按优先级进行排序输出。
系统设计类图:
在处理题目分类的时候,我采取的处理方法是将题目类抽象出来,提供通用的属性和方法供不同的题目类继承,这样做的好处是提供了一种模板机制,通过定义一些通用的行为和属性,而具体的实现则留给子类来完成。这样在不同题目继承抽象类后,他们的方法虽然不同,但是有一样的名字,不需要设计不同的名字引起歧义,同时也可以拥有自己的新增属性,从结构化的角度来说是有利于程序运行的。`abstract class Question{ protected int questionNum; // 题号 protected String question; // 题目内容 public Question(int questionNum, String question); public int getQuestionNum(); public String getQuestion(); public abstract boolean isCorrect(String answer); // 判断答案是否正确的方法,子类实现 public abstract int getScore(String answer, int fullScore); // 获取题目得分的方法,子类实现 }`
其次,处理判题时的部分正确问题,由于作答答案和标准答案可能存在顺序上的不同,一开始我的设计是通过if结构判断,或者采用二级链表进行存储,但是这样无论是在代码量还是存储量上都很大,于是我在经过网上查找资料后,发现JAVA提供了contains()方法,这是Set接口中的一个方法,用于检查调用该方法的集合中是否包含指定的元素。如果包含,则返回true,如果不包含,则返回false。这样,判定题目正确/错误/部分正确的问题就解决了,得分问题也解决了。
`public int getScore(String answer, int Score){
if(answer.trim().equals(standAnswer_Set)) return Score;
else if(standAnswer_Set.contains(answer.trim()) && !answer.trim().isEmpty()){
return Score / 2; //部分正确得一半分
}
return 0;
}`
最后是优先级排序输出问题,要实现answerSheet集合中的Answer对象首先按照学生ID排序,然后在学生ID相同的情况下,按照试卷ID排序,而JAVA中正好也存在这样一个方法,可以实现我们需要的排序。Comparator.conparing()方法是Java中用于创建一个比较器(Comparator)的方法,该比较器根据提供的函数对对象进行比较,thenComparing()方法则是用来在第一个比较条件相同的情况下,提供第二个比较条件,从而实现优先级输出。
answerSheet.sort(Comparator.comparing(AnswerSheet::getStudentID).thenComparing(AnswerSheet::getPaperId));
2.2 家居强电电路模拟程序
2.2.1 模拟1
在模拟1中涉及对整个电路的结构设计,在参考设计后,我设计了一个二级继承模型,抽象类设备CircuitDevice,抽象类受控设备和控制设备分别继承设备类,具体的开关、分档调速器、白炽灯等则继承控制设备和受控设备。此外,我还创建了一个Circuit类用来保存电路信息,这是系统设计类图:
顺序图如下:
在模拟1中,主要存在这样几个要点:
1. 计算电路中各元器件的电压,并对其状态进行更新
这一部分的实现主要是通过updateDevices()函数完成的,在这个函数中主要有三个数据结构,分别存储设备、串联电路和各元器件电压。
public Map<String, CircuitDevice> devices = new HashMap<>();//存储电路中的电路设备
public List<String[]> connections = new ArrayList<>();//存储串联电路
public Map<String, Double> voltages = new HashMap<>();
由于电路中可能存在的可以改变电压的控制元件,因此需要优先判断设备的归属问题,如果是开关,则讨论开关状态,闭合则继续,否则将VCC的输出改为0.0,由于所有设备默认都是未开启状态,因此不需要对后续设备继续维护,直接结束输出即可;如果是连续调速器或者分档调速器,则需要按照挡位对VCC的输出进行更新;如果只是普通元器件,如白炽灯等,则按照类中的方法更新设备状态。
2. 注重串联电路的电路特点,关注元器件的先后顺序
由于在约束条件中规定:对于调速器,其输入端只会直连VCC,不会接其他设备,且整个电路中最多只有一个调速器连接在电源上。因此只需要对剩余元器件进行判定,举个例子,在串联电路的末端有开关,且开关没有闭合,此时电路处于断路状态,不应该对设备进行更新,这种情况应该避免,因此需要关注元器件的先后顺序,如果toDevice是开关,则应该将inputVoltage置为0.0,然后对前面的设备重新进行更新。
else if(toDevice instanceof Switch ){
Switch sw = (Switch) toDevice;
if (!sw.on) {
inputVoltage = 0;
fromDevice.updateState(inputVoltage);
}
}
3. 设备更新方法
值得注意的是,由于输入格式和顺序问题,我们采用了fromDevice和toDevice来进行设备判定和更新,在判定完设备类别以后,以开关为例,可以利用强制类型转换新建一个开关,对这个新建的开关执行状态转换函数,而不是从列表中取出设备,然后对原有设备进行状态转换。这样操作同样可以达到维护设备列表的目的,而且易于书写,同时增加了代码的可读性。
if(message.startsWith("#K")){
String id = message.substring(1);
Switch sw = (Switch) devices.get(id);
if(sw != null) sw.toggle(); //开关状态转换
}
4. 按照规定的顺序进行输出
对电路基本判定和设备信息进行修改后就可以进行输出了,本次输出需要按照规定的顺序对设备信息进行输出。一开始我的设计是为每种设备设计输出优先级的属性,在每个变量初始化时在构造函数中规定优先级,但是这样做在编译时常常产生错误,因为我最终放弃了这种方法。在经过资料查找后,我采用的方法是,先建立哈希表为不同设备建立优先级,然后重写sort()函数,实现按照题目规定的顺序进行输出。
arr.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
String prefix1 = o1.substring(0, 1);
String prefix2 = o2.substring(0, 1);
int order1 = orderMap.getOrDefault(prefix1, 0);
int order2 = orderMap.getOrDefault(prefix2, 0);
if (order1 != order2) {
return Integer.compare(order1, order2);
} else {
return o1.compareTo(o2);
}
}
});
2.2.2 模拟2
在模拟2中,电路中会出现并联电路,同时新增了一种设备,最大的变化是,受控设备新增电阻属性。在总体结构上,新增的落地扇类只需要继承受控设备类即可,而并联电路则需要在Circuit类中新增一个哈希表来进行存储,但是由于并联电路是由串联电路组成,因此存储时将串联电路id存入,最后从串联电路的哈希表中取出即可。
在模拟2中主要存在这样几个要点:
1. 判断串联电路和并联电路的状态
串联电路存在三种状态:有电阻联通(正常),无电阻联通(短路)和断路。并联电路也存在三种状态:被支路短路,支路均联通(正常),所有支路全断(断路)。由于状态都有三种,因此在判断状态函数中不宜用boolean作为返回值类型,而是采用了int。
判断串联电路:如果所有控制元器件都正常(开关闭合,调速器挡位不为0),并且串联电路电阻不为0,那么串联电路为正常;如果有控制元件断开,串联电路断路;如果所有控制元器件正常,但串联电路电阻为0,串联电路短路。
判断并联电路:由于并联电路是由串联电路组成的,只需要循环遍历所有串联电路再判断并联电路状态即可。如果所有支路均为断路,则并联电路断路;如果存在支路短路,则并联电路短路;否则并联电路正常。
2. 计算串联电路和并联电路的电阻
在计算电阻时会利用电路状态,这样在后续电路设备更新的时候可以得到便利。
串联电路:如果串联电路无电阻联通,返回0.0;如果断路,返回double类型的最大值Double.MAX_VALUE;如果有电阻联通,返回所有元器件的电阻值的和即可。
并联电路:并联电路断路,返回double类型的最大值Double.MAX_VALUE;并联电路短路,返回0.0;并联电路正常,利用并联电路的电阻计算公式进行计算即可,无需在意存在支路断路,若支路断路,利用double类型最大值进行计算对结果产生的误差很小。
3. 电路设备状态更新
由于本次迭代考虑电阻,因此我们需要选择电压或者电流进行辅助计算。我选择的是电流。
首先判断总干路状态(利用串联电路状态判断函数),如果状态正常(有电阻联通),才继续更新设备状态,否则直接结束输出即可。
然后对VCC的输出电压进行更新,VCC后如果连了调速器,则应该根据调速器挡位进行电压的更新。这一步进行完后就可以得出干路总电流。
随后就可以开始逐个对设备进行更新,控制设备与模拟1的更新相同,在受控设备更新时,需要注意的是用压降作为传参的依据,由于模拟1中不考虑电阻,因此直接用前端设备的输出电压进行更新即可,而本次更新则需要用压降作为传参的依据。同时,在设备更新完之后,需要对volt进行更新,因为产生了压降,因此后端设备的输入电压必须更新才能存放。
如果再遍历干路设备时遇到了并联电路,那么将压降进行传参对并联电路进行更新,并联电路的更新只需要遍历每一条支路,用压降对支路设备进行设备更新即可(这个地方可以参考模拟1中的更新)。由于有并联电路的电阻计算函数,对其进行压降计算也是很方便的。
3. 练习心得
3.1 数据问题
在几次输出时,总是遇到样例结果与输出结果有一点偏差的结果,是因为规定中要求对计算结果进行下取整,而我则是在输出的时候对数据位数进行了规定,这可能导致了输出结果又有偏差。查阅资料后我发现对计算结果进行下取整有一个非常简便的方法就是在等式右边用(int)进行强制类型转换,这样就不会产生结果偏差了。
speed = (int) (4 * inputVoltage) - 240;
3.2 封装和重用
在模拟2中,考虑到每次遍历的目的和结果都不同,每当遇到电阻问题和状态判定问题时我都采用循环遍历的方式进行,但是这样做的结果是代码非常冗余,没有贯彻封装和重用的思想。经过思考以后,我将串并联的状态判定和电阻计算都封装成函数,这样以后大大减少了我的代码量,同时优化了我的结构,经过思考,这样做也是有利于后续迭代的,希望以后能贯彻这样的思想。
public int judgeSeries(String id);
public int judgeParallel(String id);
public double parallelResistance(String id);
public double calculateResistance(String id);
3.3 实际性
此次的练习是模拟家居强电电路,但是在一开始的代码实现过程中我没有好好考虑串并联电路的特性,一个是电路状态判定的多样性,串并联电路都应该有三种电路状态,而一开始我只考虑了两种,因此得分很低,二是电路顺序问题,如果存在后序的控制设备,应该对前端设备进行再更新。这两个问题都是非常实际的,而在代码编程的时候也不能忽略模拟内容在实际上的多样性。
4. 改进建议
在模拟2中,有两种模拟策略,一种是电流,一种是分压,我选择的是电流。但是这样做会使我的电阻列表用处变小,在结构上和设计上存在缺陷,因此我认为可以考虑采取分压的方式。在电阻列表中存储系统总干路每一部分的电阻,然后就可以用串联电路的分压特点计算出每一部分的压降,这样做的好处是减少了一定的误差风险。因为在电流计算上可能存在误差,电阻的计算上也可能存在误差,在计算压降的时候,很可能将两次误差重叠而产生更大而偏差。
第二个改进建议是,在存储电路时,总是根据输入的引脚存储,实际上我并不是根据引脚来进行设备状态更新的,因此我并不需要那么详细的存储设备引脚信息。在模拟1中我存储的较为详细,在模拟2中我则改为了较为简便的只存储后方内容,这样不仅可以减少存储量,还可以简化引用方式和代码。
5. 总结
经过这几次的JAVA练习,我对程序设计和JAVA编程有了更深刻的理解。首先时在程序设计时要考虑系统的总体框架,在设计建议中只有单层继承,我在设计时选择二层继承,因此在模拟2中,我只需要对总设备、控制设备和受控设备两个抽象类进行简要调整即可,不需要有大规模的结构调整。
其次是程序逻辑运行方面,在这次的练习,尤其是电路模拟练习中,由于代码量比较大,且测试点繁多,我在进行代码调整时需要手动运算程序运行结果,亲自运行梳理后才能发现其中的逻辑错误,也让我在代码设计的时候谨慎了很多。
最后是编程能力,经过这几次的练习,我的编程能力得到了很大的提高,对设计和逻辑思维有了更深刻的理解。
标签:串联,题目,String,PTA,电路,并联,更新,JAVA,设备 From: https://www.cnblogs.com/zhujie-7233/p/18563262