前三次作业总结

一、前言
在这三次题目集中,最后一题都是关于答题判题程序的代码,这些题目要求在符合特定格式约束的条件下,根据输入信息准确判断答题结果。对我而言,这类题目难度颇高,我在编写过程中经常陷入逻辑混乱,不得不进行大量的修改和调整。

第一次题目主要引导我使用面向对象的方法来解决问题,让我初步了解了类的设计、对象的创建以及方法的使用。第二次题目则增加了复杂性,要求处理更加复杂的输入格式和逻辑判断,同时还需要考虑错误处理和警告信息的输出。到了第三次题目,不仅增加了学生信息和答卷信息的处理,还要求实现删除题目信息的操作,这对我来说是个挑战。

以下是对这三次作业的详细分析。

二、设计与分析
第一次作业
UML类图设计:

(注:类图上省略了大多数的get、set方法,仅展示了相对主要的部分)
Topic类:表示单个题目,包含题号ID、内容content、标准答案standardAnswer。

  • judge_answer(): 判断答案是否正确的布尔类型方法。

TopicPaper类:试卷类,表示题目的集合,包含题目列表、题目数量。

  • addTopic(Topic topic):向列表中添加一个新的主题。
  • sortTopic():对列表中的主题进行排序。
  • getTopicById(int id):根据提供的ID获取对应的主题信息
  • judgeCorresponding(int topicNumber, String answer): 根据题目编号和答案判断是否匹配。

AnswerPaper类:答卷类,用于封装答题信息,包含试卷、答案列表、判题列表。

  • addAnswer(String oneAnswer): 添加一个答案到answer列表。
  • addResult(boolean oneResult): 添加一个结果到result列表。
  • judge(): 判定答案是否正确。
  • outputAll(): 输出所有的答案和结果。

Read类:表示读取器,主要负责输入数据的读入。

  • inputAll(): 输入所有数据。
  • getAnswerPaper(): 获取AnswerPaper对象。

第二次作业
UML类图设计:

  • 试卷类增加了getTotalScore方法来计算试卷的总分。
  • 答卷类新增了List scores属性,用于存放分数,相应的增加了judg方法,在这里判断相应的得分并保存。
  • Read类增加了存放警告信息和答卷内容的List列表,将处理后的数据收集起来集中输出。
    这次类的设计和第一次作业一模一样,我没有去添加什么新的类,只是在原有类的基础上加了些方法和属性来完成要求。这里应该是需要稍作改进的,尤其是inputAll()这个部分,它被我写的非常庞大,其实它本来只是一个输入函数,拿来读取输入数据的,但我在这里让它执行了很多操作,各个数据的处理也在这里完成。虽然这样写短期内满足了功能需求,但实际上这种设计导致了代码结构的混乱,给我的第三次作业带来了非常大的麻烦,让我不得不在之后更改代码结构。

第三次作业
UML类图:

  • 新增了Student类来表示学生信息。
  • 在Read类中增加了处理学生信息、答卷信息和删除题目信息的逻辑。
    以下为SourceMontor的生成报表内容:

    可以看见大部分的方法的复杂度较低,主要集中在1到4之间,但其中main().inputReader.getAnswerSheets() 方法其复杂度达到了20,语句数51,最大深度7,且被调用了37次,在这个代码中,它负责处理大量的输入数据并对其分析,所以有复杂的逻辑。不过可以考虑将这部分再拆分一下,让它的功能更加简单化。

三、踩坑心得及改进
1. 第一次作业
这是我第一次要去采用面向对象的方法来解决问题,不知道这些类到底该怎么分,在这一次作业我的设计几乎完全是按照题目提示来的。在解决了输入流读取的问题后,我发现测试样例5的输出顺序总是反的。

public ArrayList<Topic> getTopicList() {
        return new ArrayList<>(this.topicList);
    }

public ArrayList<Topic> sortTopic() {
        ArrayList<Topic> sortedTopic = new ArrayList<>(this.topicList);
        Collections.sort(sortedTopic, Comparator.comparingInt(Topic::getID));
        return sortedTopic;
    }

一开始我的代码在输出部分使用getTopicList()获取题目组,此时若给出的输入中题号是无序的话,我的输出会完全按照输入顺序来,而不是根据序号输出,后加上一个排序方法让输出内容可以按照题号顺序输出。

2. 第二次作业
从这里开始我就没拿过满分了。这一次的题主要是两个地方的错,一个是输出警告的顺序,一个是读入数据。
读入数据的异常没什么好说的,主要就是不能使用空格分割,将其换成正则表达式就没什么问题了。
关于输出警告顺序,见以下输出样例:

alert: full score of test paper1 is not 100 points
alert: full score of test paper2 is not 100 points
3+2=~5~true
2+2=~22~false
7 0~7
2+2=~5~false
1+1=~4~false
answer is null
0 0 0~0

可以看见,题目要求不满100分的警告需要全部在最上面输出,同时答案为空的又要跟在题目内容后边。为了让alert可以全放在最上边输出,我定义了一个List用于存储所有警告信息,在这里判断并收集。

// 检查试卷总分是否为100并记录警告信息
                if (paper.getTotalScore() != 100) {
                    alertMessages.add("alert: full score of test paper" + paperId + " is not 100 points");
                }

理论上来讲,将这些收集到的警告信息放在input.close()后输出是可以达成这个目的的,但实际上,它们却被放在了最后面。因为我在执行读入数据操作的同时进行了这些数据的处理,直接就让它们输出了。之后我再次增加了一个List来收集所有答卷的信息,把这些需要输出的地方都挪到了Main函数里,这样问题算是解决了。

3. 第三次作业
这一次的作业直到现在我还没有调试完。由于这次的输入过于混乱,各种错误输入样例层出不穷,我的代码根本扛不住这样的错误测试。
题目中增加了一个“学生信息”的输入,包括学生的学号和姓名,同时答卷信息中也多了一个学号,最后还要有删除题目信息的操作。
在之前说过,我更改了读入数据这部分的结构,这是更改之后的代码:

private void processLine(String line) throws Exception {
        if (line.startsWith("#N:")) {
            handleNewQuestion(line);
        } else if (line.startsWith("#T:")) {
            handleNewTestPaper(line);
        } else if (line.startsWith("#X:")) {
            handleNewStudent(line);
        } else if (line.startsWith("#S:")) {
            handleNewAnswerSheet(line);
        } else if (line.startsWith("#D:")) {
            handleDeleteQuestion(line);
        } 
    }

这里在判断完开始的两个字母后进入相应的执行操作,这些数据正确地分割存储都在handle方法中执行。在改完之后,我才成功地读到了输入的数据,终于不报非零返回了。不过 handleNewAnswerSheet(line)中有一些小问题,我在判断了这一行的输入属于"#S:"后就根据“#A”开始分割,而后直接提取了第一部分作为题号将其存储进答卷列表中,导致我的程序一直在报找不到对应题号的错。在调试中才发现程序把题号+学号一起存进了列表里,没能正确识别。
之后是答案的关联问题,我是根据题目顺序从 answerSheet 中提取答案的,但如果提供的答案顺序与题目顺序不一致,就会导致匹配失败,改进后增加了根据题号来提取该题的答案,通过题号来把答案与题目对应上,而不是仅仅依赖于对题目的顺序。

if (answerSheet.getAnswers() != null) {
            for (String ans : answerSheet.getAnswers()) {
                String[] answerParts = ans.split("-");
                if (answerParts[0].equals(questionNumber)) {
                    answer = answerParts[1]; // 提取该题的答案
                    break; // 找到答案后跳出循环
                }
            }
        }

好了,在这之后算是通过了第一个测试样例了。

接下来是错误样例测试,给出如下输入,要求答卷中的学号信息不在学生列表中,也要继续打印题目的得分结果。

#N:1 #Q:1+1= #A:2
#T:1 1-5
#X:20201106 Tom
#S:1 20201103 #A:1-5 #A:2-4
end

调整答案结果输出部分的逻辑,在处理每份答卷时调整输出顺序,这里是修改后的代码片段:

// 输出每道题的结果
    for (String answerResult : answerResults) {
        System.out.println(answerResult);
    }
    // 检查学生ID是否存在
…………

将输出每道题的结果这一部分拿出来放在检查学生ID之前,确保其答案照常输出。
除了这两个错误,这道题新要求的删除题目信息也有问题,不过这部分我依旧没有调试出来,现在还处于非零返回的报错中。

这几次实验中遇到的问题及解决总结如下:

  • 输入格式问题:每次题目集的输入格式都有所不同,这导致我在读取数据时经常报错。为了解决这个问题,我使用了正则表达式来分割每一行的输入数据,确保数据的正确读入。
  • 逻辑混乱问题:在处理复杂的逻辑判断时,我最初常常试图将所有逻辑都集中在一个方法中实现,但这样会导致方法过于庞大且难以维护,逻辑混乱不堪,一旦出现问题便难以迅速定位。需要尽量将复杂的逻辑拆分成多个小的方法,每个方法只负责一个具体的任务,这样在查找问题时逻辑也能够更加清晰,提高了代码的可读性和可维护性。
  • 程序可读性问题:这几次的代码我都没有写相应的注释,让我在后续的代码阅读和分析中遇到了不少麻烦。之后我会尽量写一些详细的注释以便日后复盘分析。

四、总结
这三次作业中我遇到的一大难点就是对输入数据的读取,在处理这些数据时,我起初感到无所适从,因为题目中给出的数据格式多样且复杂,如何有效地提取出有用的信息成为了一个棘手的问题,不过在解决完这个后我也对正则表达式有了了解。此外,在这几次作业的引导下,我对分析题目,设计类图有了一定的心得。要想有效地解决复杂问题,首先需要对其进行深入的分析和拆解,明确问题的核心要求和边界条件。然后,通过设计类图来清晰地表达问题的解决方案,包括类的定义、类之间的关系以及类的行为等。这个过程不仅有助于我更好地理解问题,也提高了我的设计能力,至少以后再碰到这类复杂的问题能够有一点思路。

posted @ 2024-10-26 23:29  段诗琪  阅读(62)  评论(0)    收藏  举报