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_NparseQuestions_ZparseQuestions_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,去设置其短路、断路、通路的状态,整个过程还是比较复杂的,就是因为我敲代码之前没规划好。

  设置了标志位之后,在计算电阻、电流、电压差时都对其进行判断。

   修改了这一部分逻辑之后,就通过了大部分测试点。

   但最后还是有三个测试点没过,不知道为啥,可能又漏掉了一些细节吧。

四、改进建议

对相应题目的编码改进给出自己的见解,做到可持续改进

  第四次大作业的类设计,可以设置成一个基础题目父类,然后派生出普通题、多选题、填空题三个子类,而不是直接设置一个标识符来区分是什么类型的题目,当初对继承不是很了解,现在熟悉了之后,才知道继承的重要和方便。

  写代码之前,一定一定要先多读几遍题目内容,起码对题目框架有个清晰的认识,然后也可以把思路和考虑到的各种情况都记录下来,多读几遍题目,边读边补充遗漏的、没考虑到的情况。

  有的类实现的功能太多了,维护和修改起来不是很方便,下次可以在实现多个功能的类里面设计内部类。

五、总结

对本阶段三次题目集的综合性总结,学到了什么,哪些地方需要进一步学习及研究。

  通过这三次大作业的编写,我对面向对象编程的核心概念,包括继承、多态和封装有了更清晰的认识,也真正意识到了它们的重要和方便,这些是只学理论所不能体会到的,只有自己真正实践运用起来,才能体会到那些理论知识的意义和重要性。

  还有,测试也很重要,一个好的测试样例可以帮你找到程序中的错误和不足,我也应该学会编写不同情况下的测试样例,虽然这几次我都是在向同学询问测试样例。

 

posted @   在凤凰岭吃粉丝的猫  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示