首页 > 编程语言 >南昌航空大学-软件学院-22207112-卢翔-JAVAPTA(4-6)博客

南昌航空大学-软件学院-22207112-卢翔-JAVAPTA(4-6)博客

时间:2024-11-23 18:22:05浏览次数:11  
标签:题目 JAVAPTA 编号 卢翔 22207112 构造函数 方法 输入 设备

前言

PTA第四次作业

设计与分析

题目分析

本题在答题判题程序3基础上新增了选择题和填空题内容,程序输入信息分五种,

输入格式:
1、题目信息格式:"#N:"+题目编号+" "+"#Q:"+题目内容+" "#A:"+标准答案
2、试卷信息格式:"#T:"+试卷号+" "+题目编号+"-"+题目分值+" "+题目编号+"-"+题目分值+...
3、学生信息格式:"#X:"+学号+" "+姓名+"-"+学号+" "+姓名....+"-"+学号+" "+姓名
4、答卷信息格式:"#S:"+试卷号+" "+学号+" "+"#A:"+试卷题目的顺序号+"-"+答案内容+...
5、删除题目信息格式:"#D:N-"+题目号

输出格式:
1、试卷总分警示
2、答卷信息
3、判分信息
4、被删除的题目提示信息
5、题目引用错误提示信息
6、格式错误提示信息
7、试卷号引用错误提示输出
8、学号引用错误提示信息

分析:在输入方面的改进,新增题目类型,有选择题题目信息,填空题题目信息。在输入顺序上也有变化,作业4明确只要是正确格式的信息,可以以任意的先后顺序输入各类不同的信息,比如试卷可以出现在题目之前,删除题目的信息可以出现在题目之前等。
输出方面的改进有多选题和填空题的判题结果及给分方式细化。填空题和多选题增加了 “partially correct” 表示部分正确。给分方式上添加了部分正确答案且不含错误答案给一半分。
还要考虑多张试卷及多学生答卷情况:考虑了多个同学有多张不同试卷的答卷的情况,输出顺序优先级为学号、试卷号,按从小到大的顺序先按学号排序,再按试卷号。
所以,通过题目分析,代码方面需要增加

1、题目类型判断及处理逻辑:需要增加代码来识别输入的题目是选择题还是填空题,然后根据各自不同的判题和给分规则进行处理。对于选择题,要能够处理标准答案中多个正确答案的情况,比如分割正确答案字符串,在判题时准确判断答案是否完全正确、部分正确或错误。对于填空题,要实现精确的字符匹配判断,以及按照填空题的给分规则准确计分。

2、信息输入顺序处理逻辑:由于允许各类信息以任意先后顺序输入,代码中需要增加逻辑来灵活处理这种无序输入的情况。需要先将所有输入信息收集起来,然后根据不同的类型标识(如 “#N:”“#T:”“#X:”“#S:”“#D:” 等)进行分类整理,再按照处理流程依次处理各类信息。

3、多张试卷及多学生答卷排序逻辑:为了实现按照学号、试卷号从小到大的顺序输出结果,需要在代码中增加相应的排序算法。

知识点解析

继承:使用了继承机制,如MultipleChoiceQuestion和FillInBlankQuestion类继承自Question类。
链表(LinkedList):使用了LinkedList来存储学生答案列表(answerList)和学生信息列表(studentList)。
哈希表(LinkedHashMap):运用了LinkedHashMap来存储题目信息(questionMap)和试卷信息(testpaperMap)。
正则表达式:大量使用正则表达式来匹配和解析输入的各种格式的字符串。
集合的遍历:对LinkedList和LinkedHashMap等集合类型进行遍历操作。
集合的排序:对answerList使用了Collections.sort()方法并传入自定义的比较器(通过Answer类实现Comparable接口定义的compareTo方法)来按照学号和试卷号的顺序对学生答案进行排序。
字符串处理:涉及到大量的字符串截取、拼接、比较等操作。通过equals()方法比较学生答案和标准答案是否相等,通过contains()方法判断字符串是否包含另一个字符串等操作来实现部分正确等情况的判断。
异常处理:部分通过格式检查实现。

调试过程

1.类的定义分析

Question类
作用:该类是所有题目类型的基类,用于表示一个通用的试题信息。它包含了题目的编号(number),题目的具体内容(content)题目的标准答案(standardAnswer)题目的有效形式(isValid),为后续不同类型题目(如选择题、填空题等)的类提供了基础结构。提供了一个默认构造函数public Question()和带参数的构造函数public Question(int number, String content, String standardAnswer, boolean isValid)。

MultipleChoiceQuestion类
继承自Question类,专门用于表示选择题类型的题目。构造函数public MultipleChoiceQuestion通过super()关键字调用了父类Question的带参数构造函数,将传入的参数传递给父类来初始化从父类继承的属性,确保选择题对象能正确初始化其通用的题目属性。

FillInBlankQuestion类
同样继承自Question类,用于表示填空题类型的题目。构造函数public FillInBlankQuestion通过super()调用父类构造函数来初始化继承的属性,保证填空题对象能正确设置通用的题目信息。

TestPaper类
用于表示一份试卷的相关信息,属性包括试卷中的题目及其对应的分值,以及计算试卷的总分等。同时使用LinkedHashMap来存储试卷中的题目编号(键)及其对应的分值(值)。定义了默认构造函数。
public void addQuestion(int questionID, int score):用于向试卷中添加一道新题,该方法将传入的题目编号和分值添加到questions映射中,并同时更新试卷的总分(通过累加分值)以及题目数量(自增)。
public LinkedHashMap<Integer, Integer> getQuestions():返回存储试卷题目编号和分值的LinkedHashMap,以便在其他地方获取试卷的题目信息。
public int getFullScore():通过遍历questions映射中的所有分值并累加,计算并返回试卷的总分。

Answer类
用于表示学生对试卷的答案信息,包括答案对应的题目、学生的学号、试卷编号等,同时实现了Comparable接口以便对答案进行排序。
LinkedHashMap存储学生答案中题目编号(键)及其对应的答案内容(值),answerCount记录学生答案的数量,paperID表示该答案所对应的试卷编号,studentID存储学生的唯一标识符(学号)。
提供了一系列的get和set方法和构造函数:public Answer(LinkedHashMap<Integer, String> answers, int answerCount, int paperID, int studentID),同时
@Override public int compareTo(Answer other):实现了Comparable接口的compareTo方法,用于比较两个Answer对象的大小,使Answer对象可以按照学号和试卷编号的顺序进行排序。

Student类
作用:用于表示学生的基本信息,主要包含学生的学号studentID和姓名name两个属性。

ArrayChecker类
该类提供了一个方法用于检查两个字符串数组之间的关系,主要用于处理选择题答案的比较情况(判断学生答案与标准答案的匹配程度)。
public String checkArrays(String[] array1, String[] array2)通过比较两个集合的内容来判断它们之间的关系。分别返回"true","partially correct","false"。用于判断选择题学生答案与标准答案的匹配情况,是核心功能。

NChecher类("#N:")
用于处理普通题目信息的输入,提供一个静态方法public static void processEntryForNormalQuestion(String entry, LinkedHashMap<Integer, Question> questionMap),根据特定的输入格式解析普通题目信息,并将其存储到LinkedHashMap类型的题目映射中。

ZChecher类("#Z:")
专门用于处理选择题题目信息的输入,与NChecher类类似,针对选择题的输入格式进行解析和处理。

KChecher类("#K:")
用于处理填空题题目信息的输入,按照填空题的输入格式要求解析输入字符串,并将解析出的信息存储到题目映射中。

TChecher类("#T:")
负责处理试卷信息的输入,根据特定的试卷信息输入格式解析输入字符串,并创建相应的TestPaper对象,将其存储到LinkedHashMap类型的试卷映射中。

SChecher类("#S:")
用于处理学生答案信息的输入,按照学生答案的输入格式解析输入字符串,并创建相应的Answer对象,将其添加到LinkedList类型的答案列表中。

XChecher类("#X:")
用于处理学生信息的输入,根据特定的学生信息输入格式解析输入字符串,并创建相应的Student对象,将其添加到LinkedList类型的学生列表中。

DNChecher类("#DN:")
用于处理将题目标记为无效的输入操作,根据特定的输入格式解析输入字符串,找到对应的题目并将其有效性设置为false。

OutputHandler类
提供了一系列静态方法用于输出各种错误信息和提示信息。

CheckQuestion类(核心方法)
提供了一个静态方法用于检查学生答案与题目标准答案的匹配情况,根据题目类型(选择题、填空题或其他类型)采用不同的判断逻辑来确定答案是否正确,并返回相应的判断结果。
public static String checkAnswer(Question question, String studentAnswer, String[] studentAnswersArray1):该方法首先判断题目类型,如果是MultipleChoiceQuestion类型,通过ArrayChecker类的checkArrays方法来比较学生答案数组与标准答案数组的关系,得出判断结果。如果是FillInBlankQuestion类型,则根据学生答案与标准答案的具体内容进行比较,考虑完全匹配、部分匹配等情况得出判断结果。对于其他类型的题目,直接比较学生答案与标准答案是否相等来确定判断结果。最后返回相应的判断结果字符串("true"、"partially correct"或"false")。

2.重点代码代码具体分析

不同类型输入处理部分:
处理普通题目信息(NChecher):如果输入以"#N:"开头,会调用NChecher类的processEntryForNormalQuestion方法。该方法首先通过正则表达式检查输入格式是否符合"#N:(\s\d+\s)#Q:(.)#A:(.)"的模式。若格式正确,会从输入字符串中提取出题目编号、内容和标准答案等信息,创建一个Question对象(并设置为有效),最后将该对象存入questionMap中,以题目编号作为键。若格式不正确,则输出错误提示信息"wrong format:" + entry。

class NChecher {
    public static void processEntryForNormalQuestion(String entry, LinkedHashMap<Integer, Question> questionMap) {
        if (entry.startsWith("#N:")) {
            if (entry.matches("#N:(\\s*\\d+\\s*)#Q:(.*)#A:(.*)")) {
                String num = entry.substring(entry.indexOf("#N:") + 3, entry.indexOf("#Q:")).trim();
                String content = entry.substring(entry.indexOf("#Q:") + 3, entry.indexOf("#A:")).trim();
                String standardAnswer = entry.substring(entry.indexOf("#A:") + 3, entry.length()).trim();
                int number = Integer.parseInt(num);
                questionMap.put(number, new Question(number, content, standardAnswer, true));
            } else {
                System.out.println("wrong format:" + entry);
            }
        }
    }
}

处理选择题题目信息(ZChecher):当输入以"#Z:"开头时,ZChecher类的processEntryForMultipleChoiceQuestion方法会被调用。它先检查格式是否符合特定正则表达式模式,若符合,提取相关信息创建MultipleChoiceQuestion对象(设置为有效),并存入questionMap中;否则输出错误提示。

class ZChecher {
    public static void processEntryForMultipleChoiceQuestion(String entry, LinkedHashMap<Integer, Question> questionMap) {
        if (entry.startsWith("#Z:")) {
            if (entry.matches("#Z:(\\s*\\d+\\s*)#Q:(.*)#A:(.*)")) {
                String num = entry.substring(entry.indexOf("#Z:") + 3, entry.indexOf("#Q:")).trim();
                String content = entry.substring(entry.indexOf("#Q:") + 3, entry.indexOf("#A:")).trim();
                String standardAnswer = entry.substring(entry.indexOf("#A:") + 3, entry.length()).trim();
                int number = Integer.parseInt(num);
                questionMap.put(number, new MultipleChoiceQuestion(number, content, standardAnswer, true));
            } else {
                System.out.println("wrong format:" + entry);
            }
        }
    }
}

处理填空题题目信息(KChecher):输入以"#K:"开头的情况,由KChecher类的processEntryForFillInBlankQuestion方法处理。先验证格式,格式正确则提取信息创建FillInBlankQuestion对象(设置为有效)放入questionMap中,格式不对就输出错误信息。代码逻辑和class ZChecher相似。

处理试卷信息(TChecher):若输入以"#T:"开头,TChecher类的processEntryForTestPaper方法负责处理。它先通过正则表达式判断格式是否符合要求,若符合,先提取试卷编号,创建TestPaper对象,再从输入中解析出题目编号和分值信息添加到该试卷对象中,最后将试卷对象存入testpaperMap中;若格式不符,输出错误提示。

class TChecher {
    public static void processEntryForTestPaper(String entry, LinkedHashMap<Integer, TestPaper> testpaperMap) {
        if (entry.startsWith("#T:")) {
            if (entry.matches("#T:\\s*(\\d*)\\s*(\\s*\\d+-\\d+\\s*)*")) {
                Pattern pattern1 = Pattern.compile("#T:\\s*(\\d+)\\s*(.*)");
                Matcher matcher1 = pattern1.matcher(entry);
                if (matcher1.find()) {
                    int pid = Integer.parseInt(matcher1.group(1).trim());
                    TestPaper paper = new TestPaper();
                    Pattern pattern2 = Pattern.compile("(\\d+)-(\\d+)");
                    Matcher matcher2 = pattern2.matcher(entry);
                    while (matcher2.find()) {
                        int qid = Integer.parseInt(matcher2.group(1));
                        int score = Integer.parseInt(matcher2.group(2));
                        paper.addQuestion(qid, score);
                    }
                    testpaperMap.put(pid, paper);
                } else {
                    System.out.println("wrong format:" + entry);
                }
            }
        }
    }
}

处理学生答案信息(SChecher):对于以"#S:"开头的输入,SChecher类的processEntryForStudentAnswer方法会进行处理。该方法首先检查格式是否匹配"#S:\s(\d+)\s+(\w)\s(#A:\s(\d+-?[^#]))"的正则表达式模式。若格式正确,会提取出学生的学号、试卷编号、答案数量以及每个答案的题目编号和内容等信息,创建Answer对象并添加到answerList中;若格式错误,输出错误提示。代码逻辑和class TChecher相似。

处理学生信息(XChecher):当输入以"#X:"开头时,XChecher类的processEntryForStudentInfo方法被调用。它通过正则表达式检查格式,若符合格式,会从输入中提取每个学生的学号和姓名信息,创建Student对象并添加到studentList中;若格式不正确,输出错误提示。代码逻辑和class TChecher相似。

处理题目标记为无效的操作(DNChecher):若输入以"#D:N-"开头,DNChecher类的processEntryForInvalidateQuestion方法会处理。它先检查格式是否符合"#D:N-\s\d+\s"的正则表达式模式,若符合,提取要标记为无效的题目编号,在questionMap中找到该题目并将其有效性设置为false;若格式不对,输出错误提示。代码逻辑和class TChecher相似。

对答案列表排序部分:

在完成所有输入的读取和处理后,通过Collections.sort(answerList, Collections.reverseOrder());语句对answerList中的Answer对象按照由Answer类实现的Comparable接口中的compareTo方法进行排序。

该方法的比较逻辑是先基于学生的学号(studentID)进行比较,如果两个Answer对象的学号不同,那么就根据学号的大小关系确定整个Answer对象的大小关系并返回相应结果。只有当学号相同时,才会进一步基于试卷编号(paperID)进行比较,同样根据试卷编号的大小关系确定Answer对象的大小关系并返回结果。

首先通过int sidComparison = Integer.compare(other.studentID, this.studentID)语句来比较当前Answer对象和传入的另一个Answer对象的学生学号,这个返回的整数值sidComparison就代表了基于学生学号的比较结果。(Integer.compare()方法是 Java 提供的用于比较两个整数大小的静态方法,它会根据传入的两个整数参数的大小关系返回一个整数值)

接着通过if (sidComparison!= 0)条件判断来检查学号比较结果是否不等于0。如果不等于0,说明两个Answer对象的学生学号不同,此时直接返回sidComparison的值。这样,在排序操作中,学号较大的Answer对象就会排在前面。

如果前面基于学生学号的比较结果sidComparison等于0,这意味着两个Answer对象的学生学号相同。需要进一步通过return Integer.compare(other.paperID, this.paperID)语句来比较两个Answer对象的试卷编号。

    @Override
    public int compareTo(Answer other) {
        int sidComparison = Integer.compare(other.studentID, this.studentID);
        if (sidComparison!= 0) {
            return sidComparison;
        }
        return Integer.compare(other.paperID, this.paperID);
    }
}

学生答案与题目标准答案处理部分:

ArrayChecker类中checkArrays方法:(处理答案比较)
checkArrays方法的主要目的是比较两个字符串数组array1和array2之间的关系,根据它们元素的包含情况来确定一种匹配程度,并返回一个表示匹配结果的字符串,可能的结果为"true"(完全匹配)、"partially correct"(部分匹配)或"false"(不匹配)。
在方法内部,首先将传入的两个字符串数组array1和array2分别转换为HashSet集合

    Set<String> set11 = new HashSet<>(Arrays.asList(array1));
    Set<String> set22 = new HashSet<>(Arrays.asList(array2));

然后再将这个List集合作为参数传递给HashSet的构造函数,从而创建出对应的HashSet集合。
接下来,通过一系列的条件判断来确定两个集合之间的关系,并根据不同的关系返回相应的结果字符串

if (set11.equals(set22)) {
            return "true";
        } else if ((set11.containsAll(set22) || set22.containsAll(set11))&&set22.size() < set11.size()) {
            return "partially correct";
        } else {
            return "false";
        }

完全匹配情况(set11.equals(set22)):
两个HashSet集合包含的元素完全相同,方法直接返回"true",表示两个数组是完全匹配的关系。答案完全正确。

部分匹配情况((set11.containsAll(set22) || set22.containsAll(set11))&&set22.size() < set11.size()):
我是这样思考的,首先,(set11.containsAll(set22) || set22.containsAll(set11))这部分判断的是两个集合是否存在包含关系,即要么set11包含set22中的所有元素,要么set22包含set11中的所有元素。
然后,&&set22.size() < set11.size()这部分进一步限制了条件。在前面判断出存在包含关系的基础上,还要求被包含的集合的大小要小于包含它的集合的大小。表示一种部分正确的匹配关系,例如在选择题场景下,正确答案:ABD,学生答案:AD,答案相互包含,但是学生答案少于正确答案,就可以认为是部分正确的答案。当满足这个部分匹配的条件时,方法返回"partially correct"。

不匹配情况(上述条件都不满足):
如果前面两种情况的条件都不满足,说明两个数组之间的关系是不匹配的。在这种情况下,方法返回"false",表示两个数组的元素之间不存在符合预期的匹配关系。答案完全错误。

CheckQuestion类中checkAnswer方法(处理问题类型)

checkAnswer方法的主要功能是根据传入的题目对象(Question及其子类)、学生给出的答案(字符串形式或字符串数组形式,取决于题目类型),来判断学生答案与题目标准答案的匹配情况,并返回一个表示匹配结果的字符串,可能的值为"true"(完全正确)、"partially correct"(部分正确)或"false"(错误)。
首先定义了一个空字符串变量FLAG,用于存储最终的答案匹配结果。然后根据题目类型判断答案匹配情况。
如果是选择题类型(MultipleChoiceQuestion):

if (question instanceof MultipleChoiceQuestion) {
            ArrayChecker checker1 = new ArrayChecker();
            String[] MstandardAnswersArray = question.getStandardAnswer().split(" ");
            FLAG = checker1.checkArrays(MstandardAnswersArray, studentAnswersArray1);
        } 

创建ArrayChecker对象:通过ArrayChecker checker1 = new ArrayChecker();创建了一个ArrayChecker类的对象,使用其中的checkArrays方法。

使用String[] MstandardAnswersArray = question.getStandardAnswer().split(" ");将题目对象的标准答案字符串按照空格进行拆分,得到一个字符串数组MstandardAnswersArray,每个元素代表选择题的一个正确选项。

然后比较答案数组,调用ArrayChecker对象的checkArrays方法,即FLAG =checker1.checkArrays(MstandardAnswersArray, studentAnswersArray1);,将标准答案数组和学生答案数组作为参数传入。使用checkArrays方法处理答案比较,回"true"(完全匹配)、"partially correct"(部分匹配)或"false"(不匹配)。然后将checkArrays方法的返回值赋给FLAG变量,作为选择题答案匹配的最终结果。

如果是填空题类型(FillInBlankQuestion):

 else if (question instanceof FillInBlankQuestion) {
            if (question.getStandardAnswer().equals(studentAnswer)) {
                FLAG = "true";
            } else if ((studentAnswer.contains(question.getStandardAnswer()) || question.getStandardAnswer().contains(studentAnswer)) && studentAnswer.length() < question.getStandardAnswer().length()) {
                FLAG = "partially correct";
            } else {
                FLAG = "false";
            }
        }

直接比较答案:首先通过if (question.getStandardAnswer().equals(studentAnswer))判断学生给出的答案的标准答案完全相等。如果相等,将FLAG变量设置为"true",表示答案完全正确。
部分正确判断:如果答案不完全相等,接着通过else if ((studentAnswer.contains(question.getStandardAnswer()) || question.getStandardAnswer().contains(studentAnswer)) && studentAnswer.length() <question.getStandardAnswer().length())进行部分正确的判断。逻辑是如果学生答案包含标准答案或者标准答案包含学生答案,并且学生答案的长度小于标准答案的长度,那么就认为答案是部分正确的,此时将FLAG变量设置为"partially correct"。
错误情况:如果上述两个条件都不满足,即学生答案既不与标准答案完全相等,也不符合部分正确的条件,那么将FLAG变量设置为"false",表示答案错误。

如果是其他类型题目(既不是选择题也不是填空题):

  else {
            if (question.getStandardAnswer().equals(studentAnswer)) {
                FLAG = "true";
            } else {
                FLAG = "false";
            }
        }

简单比较答案:通过if (question.getStandardAnswer().equals(studentAnswer))直接比较学生答案(studentAnswer)与题目对象的标准答案是否相等。如果相等,将FLAG变量设置为"true";如果不相等,将FLAG变量设置为"false",以此来确定答案的正确性。
最后,通过return FLAG;语句将存储了答案匹配结果的FLAG变量返回给调用该方法的地方,以便根据这个结果进行输出答案的正确与否信息、计算得分等操作的处理。

3.类图时序图


改进建议

1、MultipleChoiceQuestion 和 FillInBlankQuestion 目前只是简单继承自 Question,可以考虑在子类中添加一些特定于题型的方法或属性,以更好地体现不同题型的特点和处理逻辑。
2、正则表达式的复用与优化:代码中多处使用了正则表达式来验证输入格式,如在各个 Checher 类(NChecher、ZChecher 等)中。可以考虑将一些常用的正则表达式提取成常量,以便在多个地方复用,并且在编写正则表达式时,可以进一步优化其准确性和简洁性,避免过于复杂和难以理解的表达式,同时添加更详细的注释来解释其用途和匹配规则。
3、在 CheckQuestion 类的 checkAnswer 方法中,对于不同题型答案的检查逻辑可以进一步优化。目前代码中有一些重复的逻辑判断,比如对于多选题和填空题部分逻辑相似但又分别实现。可以考虑提取公共的逻辑部分,通过多态或者其他设计模式来更优雅地处理不同题型答案的检查,减少代码的冗余。
4、在处理学生答案与问题匹配以及计算得分等逻辑中,存在一些相似的代码片段在不同的条件分支下重复出现,比如多次获取学生答案、拆分答案字符串、检查问题是否存在及有效等操作。可以将这些重复的逻辑封装成可复用的方法。

PTA第五次作业

设计与分析

题目分析

输入格式:

1、设备信息:分别用设备标识符K、F、L、B、R、D分别表示开关、分档调速器、连续调速器、白炽灯、日光灯、吊扇。引脚格式:设备标识-引脚编号。
2、连接信息一条连接信息占一行,用[]表示一组连接在一起的设备引脚,引脚与引脚之间用英文空格" "分隔。
3、控制设备调节信息
开关调节信息格式:#+设备标识K+设备编号
分档调速器的调节信息格式:#+设备标识F+设备编号+"+" 代表加一档
连续调速器的调节信息格式:#+设备标识L+设备编号+":" +数值
4、电源接地标识:VCC,电压220V,GND,电压0V。输入信息以end为结束标志,

输出格式:

按开关、分档调速器、连续调速器、白炽灯、日光灯、吊扇的顺序依次输出所有设备的状态或参数。每个设备一行。同类设备按编号顺序从小到大输出。
输出格式:@设备标识+设备编号+":" +设备参数值(控制开关的档位或状态、灯的亮度、风扇的转速,只输出值,不输出单位)
连续调速器的档位信息保留两位小数,即使小数为0,依然显示两位小数.00。
开关状态为0(打开)时显示turned on,状态为1(合上)时显示closed
分析:首先定义了一系列设备相关的类,以设备基类为基础,派生出控制设备类及其子类(开关、分档调速器、连续调速器)和受控设备类及其子类(灯、风扇的具体类型)。每个子类都根据自身设备特性定义了相应的属性和方法,比如控制设备类用于调节状态或档位以及计算输出电位的方法,受控设备类用于根据引脚电压差计算亮度或转速的方法。在电路连接逻辑上,通过特定的数据结构来存储设备间的连接关系,依据输入的连接信息格式进行解析并记录。输入处理部分设置了读取输入、解析连接信息、解析控制设备调节信息等函数,分别负责处理不同类型的输入内容,确保准确识别并执行相应操作。输出处理部分则有专门的函数按照规定顺序和格式输出各设备的最终状态或参数。最后在主函数中完成整体流程的调度,先进行输入处理,再输出所有设备的状态信息,从而完整地模拟出智能家居强电电路系统的运行情况。

知识点解析

1、类与继承:代码大量运用了类来对不同的电气设备进行抽象建模,如BaseElectricalDevice作为基类,定义了设备的一些公共属性(设备编号、设备类型)和方法(showStatus)。其他各类具体设备(如SwitchingDevice、StepSpeedRegulator等)都继承自BaseElectricalDevice,继承机制使得子类能够复用基类的属性和方法,并根据自身特性进行扩展和重写,体现了面向对象编程中代码复用和多态性的特点。

调试过程

2、链表(LinkedList):使用了LinkedList来存储多种数据,如deviceIdList用于存储设备编号列表,uniqueDeviceIdList用于存储唯一设备编号列表,connectionList用于存储连接列表,controlCommandList用于存储控制命令列表,以及deviceList用于存储设备对象列表。
3、哈希映射(LinkedHashMap):LinkedHashMap用于存储设备映射,其中键为设备编号,值为对应的电气设备对象(BaseElectricalDevice及其子类对象)。
4、正则表达式,运用正则表达式来匹配和解析特定格式的输入数据。
5、异常处理NumberFormatException:在处理需要将字符串转换为数字类型的操作时,考虑到可能出现的格式错误,通过try-catch语句捕获NumberFormatException异常。
6、Comparator 接口:实现了Comparator接口来定义设备比较器(DeviceComparator类),用于对设备列表进行排序。

1.类的定义分析

BaseElectricalDevice类
作为所有电气设备类的基类,定义了电气设备的一些基本属性和通用方法,为子类提供了一个基础框架,便于实现多态性和代码复用。deviceId用于存储设备的编号,deviceType表示设备的类型
public BaseElectricalDevice(String id, String type):用于初始化deviceId和deviceType属性。
public String showStatus(BaseElectricalDevice device):为子类提供一个可重写的模板方法,子类可以根据自身设备的具体状态或参数情况重写该方法,实现多态性的输出展示。

SwitchingDevice类
继承自BaseElectricalDevice类,专门用于模拟开关设备的行为和特性。
属性有state布尔类型变量,表示开关的状态,true表示关闭状态,false表示打开状态,初始化为false,即默认开关为打开状态。
public SwitchingDevice(String id):构造函数,调用父类的构造函数初始化设备编号和设备类型(“K”),同时设置开关的初始状态为false。
public void flipState():用于切换开关的状态,通过对state变量取反操作来实现,简单有效地改变开关的开合状态。
public double getOutputVoltage(double inputVolt):根据开关的当前状态返回输出电压,准确模拟了开关对电压传输的控制作用。
@Override public String showStatus(BaseElectricalDevice device):重写了父类的showStatus方法,按照特定格式“@设备编号:状态(closed或turned on)”返回开关的状态信息。

StepSpeedRegulator类
继承自BaseElectricalDevice类,用于模拟分档调速器设备的功能,如调节输出电压的档位。
level整型变量,表示调速器的档位,初始化为0,代表初始档位为最低档。
public StepSpeedRegulator(String id):构造函数,调用父类构造函数初始化设备编号和设备类型(“F”),并将档位level初始化为0。
public double getOutputVoltage(double inputVolt):根据当前档位level返回相应的输出电压。
public void adjustLevel(boolean increase):用于调整调速器的档位。如果increase参数为true,则将档位增加一档;如果为false,则将档位降低一档。同时,通过条件判断确保档位值在有效范围内(0到3之间),防止出现超出预期的档位设置。
@Override public String showStatus(BaseElectricalDevice device):重写父类的showStatus方法,按照格式“@设备编号:档位值”返回调速器的当前档位信息。

ContinuouslyAdjustableDevice类
继承自BaseElectricalDevice类,用于模拟连续调速器设备的特性,能够根据设定的参数值在一定范围内连续调节输出电压。
value双精度浮点型变量,表示连续调速器的参数值,初始化为0,用于确定输出电压与输入电压的比例关系。
public ContinuouslyAdjustableDevice(String id):构造函数,调用父类构造函数初始化设备编号和设备类型(“L”),并将参数值value初始化为0。
public void setParameterValue(double param):用于设置连续调速器的参数值,将传入的参数param赋值给value变量,从而改变输出电压的比例关系,实现对输出电压的连续调节功能。
public double getOutputVoltage(double inputVolt):根据当前的参数值value和输入电压inputVolt计算并返回输出电压。
@Override public String showStatus(BaseElectricalDevice device):重写父类的showStatus方法。

IncandescentLightDevice类
继承自BaseElectricalDevice类,专门用于模拟白炽灯设备的特性,根据输入电压设置并返回brightness整型变量,表示白炽灯的亮度,初始化为0,用于存储根据输入电压计算得到的亮度值。
public IncandescentLightDevice(String id):构造函数,调用父类构造函数初始化设备编号和设备类型(“B”),并将亮度brightness初始化为0。
public void setBrightness(double inputVolt):根据输入电压inputVolt设置白炽灯的亮度。通过一系列条件判断,按照不同的输入电压范围采用不同的计算公式来确定亮度值。@Override public String showStatus(BaseElectricalDevice device):重写父类的showStatus方法,按照格式“@设备编号:亮度值”返回白炽灯的当前亮度信息。

FluorescentLightDevice类
继承自BaseElectricalDevice类,用于模拟日光灯设备的特性,根据输入电压确定日光灯的亮度。brightness整型变量,表示日光灯的亮度,初始化为0,用于存储根据输入电压计算得到的亮度值。
public FluorescentLightDevice(String id):构造函数,调用父类构造函数初始化设备编号和设备类型(“R”),并将亮度brightness初始化为0。
public void setBrightness(double inputVolt):根据输入电压inputVolt设置日光灯的亮度。@Override public String showStatus(BaseElectricalDevice device):重写父类的showStatus方法,按照格式“@设备编号:亮度值”返回日光灯的当前亮度信息。

FanDevice类
继承自BaseElectricalDevice类,用于模拟风扇设备的特性,根据输入电压设置并返回风扇的转速。
speed:整型变量,表示风扇的转速,初始化为0,用于存储根据输入电压计算得到的转速值。
public FanDevice(String id):构造函数,调用父类构造函数初始化设备编号和设备类型(“D”),并将转速speed初始化为0。
public void setRotationSpeed(double inputVolt):根据输入电压inputVolt设置风扇的转速。
@Override public String showStatus(BaseElectricalDevice device):重写父类的showStatus方法,按照格式“@设备编号:转速值”返回风扇的当前转速信息。

DeviceComparator类
实现了Comparator接口,用于定义设备之间的比较规则,以便对设备列表按照特定顺序进行排序。
private static final String[] DEVICE_TYPE_ORDER = {"K", "F", "L", "B", "R", "D"};:定义了一个私有静态常量数组,用于存储设备类型的优先级顺序,按照这个顺序来确定不同类型设备之间的先后排序关系。
@Override public int compare(BaseElectricalDevice d1, BaseElectricalDevice d2):重写了Comparator接口的compare方法,用于比较两个BaseElectricalDevice类型的设备。
private int getTypePriority(BaseElectricalDevice device):私有方法,用于获取给定设备在DEVICE_TYPE_ORDER数组中的优先级索引。
private int getDeviceIdNumber(BaseElectricalDevice device):私有方法,用于获取设备编号的数字部分。

2.重点代码代码具体分析
首先设计类:BaseElectricalDevice类,SwitchingDevice类,StepSpeedRegulator类,ContinuouslyAdjustableDevice类,IncandescentLightDevice类,FluorescentLightDevice类,FanDevice类,DeviceComparator类。

创建数据结构:
在main方法中,首先创建了多个LinkedList和一个LinkedHashMap用于存储不同类型的输入信息。

// 创建一个 LinkedList 用于存储设备编号列表
        LinkedList<String> deviceIdList = new LinkedList<>();
        // 创建一个 LinkedList 用于存储唯一设备编号列表
        LinkedList<String> uniqueDeviceIdList = new LinkedList<>();
        // 创建一个 LinkedList 用于存储连接列表
        LinkedList<String> connectionList = new LinkedList<>();
        // 创建一个 LinkedHashMap 用于存储设备映射,键为设备编号,值为电气设备对象
        LinkedHashMap<String, BaseElectricalDevice> deviceMap = new LinkedHashMap<>();
        // 创建一个 LinkedList 用于存储控制命令列表
        LinkedList<String> controlCommandList = new LinkedList<>();

读取用户输入并分类处理:
通过Scanner对象从控制台循环读取用户输入,直到输入为“end”结束循环。

对于读取到的每一行输入,根据其格式进行分类处理:
如果输入以“#”开头,则将其作为控制命令添加到controlCommandList中。
如果输入以“[”开头,则通过正则表达式"(\w+)-(\d+)"匹配连接信息中的设备编号格式,提取出设备编号添加到deviceIdList中,并将完整的连接信息添加到connectionList中。
如果输入不符合上述两种格式,则输出“Wrong Format”错误提示。

处理设备编号去重及设备对象创建:
在处理完所有输入后,对deviceIdList进行去重操作,将不重复的设备编号添加到uniqueDeviceIdList中。这里通过两层循环遍历deviceIdList和uniqueDeviceIdList,比较每个元素是否相等,若不相等则添加到uniqueDeviceIdList中。
然后遍历uniqueDeviceIdList,根据设备编号的首字符判断设备类型,并创建相应类型的电气设备对象,添加到deviceMap中。

for (String str : deviceIdList) {
            isDuplicate = false;
            for (String uniqueStr : uniqueDeviceIdList) {
                if (str.equals(uniqueStr)) {
                    isDuplicate = true;
                    break;
                }
            }
            if (!isDuplicate) {
                uniqueDeviceIdList.add(str);
            }
        }

根据控制命令操作设备:
遍历controlCommandList中的控制命令,根据命令的前缀判断命令类型,并对相应设备进行操作:
如果命令以“#L”开头,则解析出连续调速器的设备编号和要设置的参数值,若设备映射deviceMap中包含该设备编号,则通过((ContinuouslyAdjustableDevice) deviceMap.get(deviceId)).setParameterValue(parameterValue)设置连续调速器的参数值,否则输出“Wrong Format”错误提示。
如果命令以“#F”开头,则解析出分档调速器的设备编号以及是升档还是降档操作,若设备映射中包含该设备编号,则通过((StepSpeedRegulator) deviceMap.get(fId)).adjustLevel(isIncrease)调整分档调速器的档位,否则输出“Wrong Format”错误提示。
如果命令以“#K”开头,则解析出开关设备的编号,若设备映射中包含该设备编号,则通过((SwitchingDevice) deviceMap.get(switchId)).flipState()切换开关状态,否则输出“Wrong Format”错误提示。
模拟电压传递:
创建一个LinkedList类型的deviceList,将deviceMap中的所有设备对象添加到其中。
设置输入电压为220,然后遍历deviceList,根据设备类型调用相应设备的getOutputVoltage方法模拟电压的传递,更新输入电压的值。

        for (BaseElectricalDevice device : deviceList) {
            switch (device.deviceType) {
                case "K":inputVoltage = ((SwitchingDevice) device).getOutputVoltage(inputVoltage);break;
                case "F":inputVoltage = ((StepSpeedRegulator) device).getOutputVoltage(inputVoltage);break;
                case "L":inputVoltage = ((ContinuouslyAdjustableDevice) device).getOutputVoltage(inputVoltage);break;
            }
        }

设置设备状态(亮度、转速):再次遍历deviceList,根据设备类型调用相应设备的setBrightness(对于灯设备)或setRotationSpeed(对于风扇设备)方法,根据当前的输入电压设置设备的状态(如白炽灯的亮度、风扇的转速等)。

  for (BaseElectricalDevice device : deviceList) {
            switch (device.deviceType) {
                case "B":((IncandescentLightDevice) device).setBrightness(inputVoltage);break;
                case "R":((FluorescentLightDevice) device).setBrightness(inputVoltage);break;
                case "D":((FanDevice) device).setRotationSpeed(inputVoltage);break;
            }
        }

对设备列表排序:
使用DeviceComparator对deviceList进行排序,使得设备按照先按设备类型优先级,类型相同再按设备编号大小的顺序排列。
输出各设备状态或参数:遍历排序后的deviceList,调用每个设备的showStatus方法,按照各自重写的格式输出每个设备的状态或参数信息。

 Collections.sort(deviceList, new DeviceComparator());
        for (BaseElectricalDevice device : deviceList) {
            System.out.println(device.showStatus(device));
        }

3.类图时序图


改进建议

设备操作逻辑优化:在处理不同类型设备的操作(如设置参数、调整档位、切换开关等)时,目前是通过多个if-else或switch语句进行判断。可以考虑使用策略模式或工厂模式等设计模式来优化这部分逻辑,使得代码的扩展性更好,当需要添加新的设备类型或操作时,更容易进行维护。
考虑使用集合框架的其他特性:在处理设备编号去重时,目前使用了双层循环来判断是否重复并添加到唯一设备编号列表。可以考虑使用HashSet等数据结构来自动去重,这样代码会更加简洁高效。
正则表达式复用:在解析连接信息和处理控制命令时,都使用了正则表达式。可以考虑将常用的正则表达式提取成常量,以便复用,同时也方便后续如果需要修改正则表达式的模式,只需要在一处修改即可。
方法抽取:在Main类的main方法中,部分逻辑可以抽取成独立的方法,例如处理用户输入并分类添加到不同列表的逻辑、根据设备编号创建对应设备对象并添加到映射的逻辑等。

PTA第六次作业

设计与分析

题目分析

迭代内容:

  1. 设备类型增加:在受控设备方面,题目 2 在原有基础上新增了 “落地扇” 这一设备类型,需要在代码中对其进行相应的模拟和处理,包括其工作电压与转速的对应关系、电阻等特性的设置。
  2. 电路连接方式拓展:题目 1 只考虑串联形式的电路连接,而题目 2 不仅考虑串联,还明确提到要考虑各类设备的并联接入情况,这使得电路连接的复杂性大大增加,需要在代码中实现对并联电路以及其包含的串联电路的解析、处理和模拟电流在并联电路中的分配等逻辑。
    3、输入信息格式改变:题目 2 的连接信息不再单独输入,而是包含在串联电路信息和并联电路信息中。串联电路信息需按从靠电源端到接地端顺序依次输入连接信息,且有特定的格式规范;并联电路信息则由其包含的几条串联电路组成,也有相应格式。
  3. 设备属性增加:题目 2 新增了各设备电阻属性,白炽灯的电阻为 10,日光灯的电阻为 5,吊扇的电阻为 20,落地扇的电阻为 20。在模拟电路工作过程中,需要根据这些电阻值来计算电流、电压分配等情况,这在代码实现上需要增加相应的属性设置和相关计算逻辑。

代码方面分析:
1、新增设备类:需要创建一个新的 “落地扇” 设备类,类似于已有的吊扇设备类,定义其工作电压与转速的对应关系、电阻属性以及根据输入电压计算转速等方法
2、设备基类属性添加:在设备基类(如BaseElectricalDevice)中需要添加一个表示电阻的属性
3、输入逻辑:需要更新代码实现串联电路信息的解析和并联电路信息的解析
4、处理并联电路逻辑:在模拟电路工作过程中,当涉及到并联电路时,需要根据并联电路的特性来更新代码逻辑
5、考虑设备电阻的计算:由于新增了设备电阻属性,在模拟电路工作时,需要根据欧姆定律等电学原理,结合设备的电阻、输入电压等信息来计算电流等参数。
6、输出部分的更新:在输出所有设备的状态或参数时,需要按照题目 2 的要求,新增对落地扇设备状态的输出,确保输出顺序为开关、分档调速器、连续调速器、白炽灯、日光灯、吊扇、落地扇,并遵循相应的输出格式规范。

知识点解析

多态性:通过在子类中重写基类的方法(showmethod、regulate等方法在不同子类中有不同的实现)来体现多态性。
ArrayList:广泛使用了ArrayList来存储各种数据,如connectionList、parallelList、controlList等。
HashMap 与 LinkedHashMap:使用HashMap(如parallelCircuits、mainCircuit、finalCircuit等)和LinkedHashMap存储键值对形式的数据。
自定义比较器(Comparator 接口):定义了内部类DeviceComparator实现Comparator接口,用于比较两个BasEledevice对象的优先级顺序。

调试过程

1. 类的定义分析
BasEledevice类:作为所有电气设备类的基类,定义了一些通用的属性和方法,这些属性和方法会被继承它的子类所共享或重写。
属性:
type:表示设备的类型。
id:设备的唯一标识符。
voltage:设备的电压,默认值为 220。
ifopen:表示设备是否开启,默认值为"turned on"。
speed:设备的速度,默认值为 0,部分子类会根据具体情况使用该属性。
value:设备的某个数值,默认值为 0.00,部分子类会根据具体情况使用该属性。
resistance:设备的电阻值,在构造函数中初始化。
方法:
showmethod():在基类中为空实现,子类会根据自身需求重写该方法以展示设备的相关信息。
regulate(String vs):在基类中为空实现,子类会根据自身需求重写该方法来实现对设备的调节功能。
setVoltage(double voltage):用于设置设备的电压值。
getReturnTypeValue():根据设备的type属性,返回一个对应的值,用于确定设备类型在优先级顺序中的索引。

SwitchDevice类:表示开关设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"K"。
重写的方法:
showmethod():重写父类方法,用于输出开关设备的开启状态信息。
setVoltage(double voltage):根据设备的开启状态来设置电压。

StepSpeedRegulator类:表示步进速度调节器设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"F"。
重写的方法:
showmethod():重写父类方法,用于输出速度调节器的当前速度信息。
regulate(String vs):根据传入的调节指令vs("+"或"-")来增加或减少设备的速度,速度范围限制在 0 到 3 之间。
setVoltage(double voltage):根据设备的速度来计算并设置电压。

ConAdjustDevice类:表示连续调节设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"L"。
重写的方法:
showmethod():重写父类方法,用于输出连续调节设备的当前调节值信息。
regulate(String vs):将传入的字符串vs转换为双精度浮点数,并赋值给设备的value属性,实现对设备的调节。
setVoltage(double voltage):根据设备的value属性来计算并设置电压。

WhiteLightDevice类:表示白炽灯设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"B",并设置设备的电阻值为 10。
重写的方法:
showmethod():根据设备的电压值计算并输出白光灯的亮度信息。
setVoltage(double voltage):调用父类的setVoltage方法设置电压。

SunLightDevice类:表示日光灯设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"R",并设置设备的电阻值为 5。
重写的方法:
showmethod():根据设备的电压值计算并输出太阳光模拟器的亮度信息。
setVoltage(double voltage):调用父类的setVoltage方法设置电压。

FanDevice类表示风扇设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"D",并设置设备的电阻值为 20。
重写的方法:
showmethod():根据设备的电压值计算并输出风扇的速度信息。
setVoltage(double voltage):调用父类的setVoltage方法设置电压。

GroundFanDevice类表示落地扇设备,继承自BasEledevice类。
构造函数:调用父类构造函数,将type设置为"A",并设置设备的电阻值为 20。
重写的方法:
showmethod():根据设备的电压值计算并输出落地扇的速度信息。
setVoltage(double voltage):调用父类的setVoltage方法设置电压。

BINGconnect类:表示并联,继承自BasEledevice类,构造函数将type设置为"M"。

CHUANconnect类:表示串联,继承自BasEledevice类,构造函数将type设置为"T"。

2. 重点代码代码具体分析

设置BasEledevice类和各类设备子类。

输入处理部分
首先创建了几个ArrayList用于存储不同类型的输入信息,分别是connectionList(用于存储电路连接相关信息)、parallelList(用于存储并行电路相关信息)和controlList(用于存储控制设备的指令信息)。

通过Scanner类从控制台读取用户输入的字符串,每次读取一行并去除首尾空格后进行处理。根据输入内容是否符合特定的格式(通过isEndInput、isTInput、isMInput、isKInput、isFInput、isLInput方法判断),将其分别交给handleTInput、handleMInput、handleKInput、handleFInput、handleLInput处理方法进行处理。

while (true) {
            input = in.nextLine().trim();
            if (isEndInput(input)) {break;}
            if (isLInput(input)) {handleLInput(input, controlList);}
            if (isFInput(input)) {handleFInput(input, controlList);}
            if (isKInput(input)) {handleKInput(input, controlList);}
            if (isMInput(input)) {handleMInput(input, parallelList);}
            if (isTInput(input)) {handleTInput(input, connectionList);}
        }

电路构建部分
处理电路连接信息(connectionList):
遍历connectionList中的每个元素,先通过正则表达式Pattern.compile("(.)

标签:题目,JAVAPTA,编号,卢翔,22207112,构造函数,方法,输入,设备
From: https://www.cnblogs.com/LX14332234/p/18560741

相关文章

  • javaPTA题目集4、5及期中考试总结
    一、前言通过这三周对Java课程的学习及pta大作业的练习,我了解了Java的编译环境如JDK、JRE等等,Java去掉了C++语言的许多功能,是安全的、解释的、高性能的语言,但最主要的还是Java的面向对象性,Java中的类与对象的创建以及类间关系,类与类之间方法属性的调用时常让我头疼,通过pta的练习......