JAVA第一次博客作业

一、前言

总结三次题目集的知识点、题量、难度等情况

这三次题目集的最后一道大作业是一系列递进的编程作业。

第一次作业 答题判题程序-1
  设计一个简单的答题判题程序,要求输入题目信息和答题信息,根据标准答案判断答题结果。
 主要知识点:

  •  基本的类设计和对象创建。
  •  输入输出处理。
  •  字符串处理。
  •  正则表达式。

第二次作业 答题判题程序-2
  在第一次作业的基础上,增加了试卷信息和题目分值,要求处理更复杂的输入数据,并根据题目分值计算总分。
 主要知识点:

  •  扩展的类设计,包括试卷类和题目类的关联。
  •  数据结构的应用,如列表和字典,用于存储和管理题目和试卷信息。
  •  算法实现,包括排序等。

第三次作业 答题判题程序-3
进一步扩展程序,增加了学生信息、删除题目信息和答题信息的处理。要求处理更复杂的输入数据,包括学生信息、试卷信息、题目信息和答题信息,并实现更复杂的逻辑。
 主要知识点:

  •  高级类设计,包括学生类、试卷类、题目类和答题类的设计。
  •  数据结构的应用,如列表、字典和集合,用于存储和管理复杂的数据。
  •  算法实现,包括排序、搜索和分数计算。
  •  系统设计,包括程序结构和模块化设计。

题量:每次作业都逐渐增加,从简单的题目信息和答题信息处理,到复杂的试卷信息、学生信息和删除题目信息的处理。
难度:随着题目的递进,难度也逐渐增加,要求掌握更复杂的类设计、数据结构应用和算法实现。

二、设计与分析

重点对题目的提交源码进行分析,可参考SourceMontor的生成报表内容以及PowerDesigner的相应类图,要有相应的解释和心得(做到有图有真相),本次Blog必须分析题目集1~3的最后一题

第一次作业 答题判题程序-1

设计类图如下:

  第一次PTA大作业难度较低,但由于这个时候我对于类的创建不是很熟悉,只想着去实现它的全部功能就好,也没有对类做过多的分析和考虑,所以这里只创建了一个Question类,而把大部分逻辑代码全部放在了Main函数里面。

程序逻辑:

  1.  初始化输入:程序开始时,读取用户输入的问题数量。
  2.  创建数据结构:根据问题数量,初始化用于存储问题对象和答案结果的数组。
  3.  读取题目信息:通过函数读取用户输入的每个问题详细信息,包括编号、内容和标准答案。
  4.  读取用户答案:通过函数读取用户对每个问题的回答。
  5.  检查结束标志:确认是否收到结束输入,如果不是"end",则终止程序。
  6.  比对答案:通过函数比对用户答案和标准答案,记录每个问题的回答是否正确。
  7.  输出结果:输出每个问题的题目内容和用户的答案,以及用户回答的正确与否。

详细设计:

Question类:

  • 属性:id题目编号、content题目内容和standardAnswer题目标准答案。
  • 方法:

    构造函数:初始化题目的编号、内容和标准答案。    

    getId():返回题目的编号。

    getContent():返回题目的内容。

    getStandardAnswer():返回题目的标准答案。

    equals(Question question):判断两个题目是否相同,依据是它们的编号是否相等。

Main类:

  • 方法:主方法控制程序整体流程,其他方法分别用于读取题目信息、读取用户答案、检查结束标志、比对答案和输出结果。

 

第二次作业 答题判题程序-2

设计类图如下:

相较于第一次大作业,这一次对题目进行了重新的规划,设计了Question、TextParser、AnswerSheet、Exam四个类。

 

在这个类图中:

  • Main类使用TestParser类来解析输入。
  • TestParser类创建QuestionExamAnswerSheet对象。
  • Exam类包含Question对象。
  • AnswerSheet类使用Exam类和Question类来计算得分和输出结果。

 主要逻辑和详细设计:

1. 初始化和输入解析

  • 使用Scanner对象从标准输入流读取数据。
  • 创建TestParser对象来处理输入的数据。

2. 输入数据的解析

  • TestParser类中的parseInput方法读取输入的字符串,根据是否以#N#T#S开头,将数据分别追加到对应的StringBuilder对象中。这里没有用String,是因为StringBuilder对象是可变的,可以动态地修改字符串的内容,而不需要创建新的字符串对象,方便我把各类语句存放到一起。
1     private StringBuilder str_N = new StringBuilder();
2     private StringBuilder str_T = new StringBuilder();
3     private StringBuilder str_S = new StringBuilder();

3. 题目信息的解析

  • parseQuestions方法将题目信息从str_N中解析出来,创建Question对象,并存储在数组questions中。
TestParser parser = new TestParser();
parser.parseInput(input);
parser.parseQuestions(); // 调用解析题目信息的方法

4. 试卷信息的解析

  • parseExams方法解析试卷信息,创建Exam对象,并为每个试卷添加题目和分值。题目从questions数组中获取,并为每个题目设置分值。
parser.parseExams(); // 调用解析试卷信息的方法

5. 答卷信息的解析

  • parseAnswerSheets方法解析答卷信息,创建AnswerSheet对象,并为每个答卷关联对应的试卷和答案。
parser.parseAnswerSheets(); // 调用解析答卷信息的方法

6. 试卷总分警示

  • 遍历exams数组,如果任何试卷的总分不为100,输出警示信息。
for (Exam e : exams) {
    if (e.CalTotalscore() != 100) {
        System.out.printf("alert: full score of test paper%d is not 100 points\n", e.getNum());
    }
}

7. 判分和输出结果

  • 遍历answerSheets数组,对每个答卷调用CalAndJudge方法判断题目正确性并计算得分,并调用printf_result方法输出结果。
  • 如果答卷对应的试卷不存在,输出错误提示。
for (AnswerSheet a : answerSheets) {
    if (a != null) {
        a.CalAndJudge();
        a.printf_result();
    }
    if (a.getExam() == null)
        System.out.println("The test paper number does not exist");
}

 

第三次作业 答题判题程序-3

设计类图如下:

 

在这个类图中:

  • Main类使用TestParser类来解析输入。
  • TestParser类创建QuestionExamAnswerSheetStudent对象。
  • Exam类包含Question对象。
  • AnswerSheet类使用Exam类和Student类来计算得分和输出结果。
  • Student类包含AnswerSheet对象,表示学生的答案信息。

主要逻辑和详细设计:

1. 初始化和输入解析

  • 使用Scanner对象从标准输入流读取数据。
  • 创建TestParser对象来处理输入的数据。
Scanner input = new Scanner(System.in);
TestParser parser = new TestParser();
parser.parseInput(input);
  • 解析学生信息、题目信息、试卷信息、答卷信息以及删除题目信息。

parser.parseStudent();
parser.parseQuestions();
parser.parseExams();
parser.parseAnswerSheets();
parser.parseDeleteQuestion();
  • 获取解析后的数据
Exam[] exams = parser.getExams();
AnswerSheet[] answerSheets = parser.getAnswerSheets();
Student[] students = parser.getStudents();

2. 输入数据的解析

  • TestParser类中的parseInput方法读取输入的字符串,根据是否以#N#T#S#X#D开头,将数据分别追加到对应的StringBuilder对象中。

由于题目新增判断输入格式是否正确,所以这里相较于第二次大作业添加了正则表达式去判断每隔类型的输入是否符合规范。

String questionPattern =  "#N:(.*) #Q:(.*) #A:(.*)";
String examPattern =  "#T:(\\d+)(?: \\d+-\\d+)*";
String studentPattern =  "#X:(\\d+ .+)*";
String answerPattern = "#S:(\\d+) (\\d+)(?: #A:\\d+-.+)*";
String deletePattern = "#D:N-(\\d+)";

3. 题目信息的解析

  • 使用split方法按#N:#Q:#A:分割输入的字符串,得到一个字符串数组parts_N
  • 计算题目数量num,每个题目由三个部分构成:题目编号、内容、标准答案。
  • 初始化questions数组,长度为num
  • 遍历parts_N数组,每三个元素构成一个题目。
  • 对于每个题目,提取编号、内容和标准答案,创建Question对象,并将其添加到questions数组中。

4. 试卷信息的解析

  • 将输入的试卷数据按行分割,得到parts_T数组。
  • 遍历parts_T数组,每行代表一张试卷。
  • 对于每张试卷,提取试卷编号,并创建Exam对象。
  • 遍历试卷中的题目信息,对于每个题目,提取题目编号和分值。
  • questions数组中查找对应的题目,如果找到,则创建一个新的Question对象,设置其分值,并将其添加到试卷中。
  • 如果在questions数组中没有找到对应的题目,则创建一个内容为"none_null"Question对象,并将其添加到试卷中。
复制代码
for (Question q : questions) {
                    if(q!=null) {
                        if (q.getId() == Ques_id) {
                            Question temp_q = new Question(q);
                            exams[i].addQuestion(temp_q);
                            temp_q.setScore(Ques_score);
                            found = true;
                            break;
                        }
                    }
                }
复制代码
// 如果没有找到题目,则添加错误题目
                if (!found) {
                    Question temp_q = new Question(); 
                    temp_q.setContent("none_null"); // 错误题号,题目内容设置为none_null
                    temp_q.setScore(0); 
                    exams[i].addQuestion(temp_q); 
                }

5. 学生信息的解析

  • 使用正则表达式分割输入字符串,获取学生信息。
  • 计算学生数量并初始化学生数组。
  • 遍历分割后的结果,提取学号和姓名。
  • 为每个学生创建 Student 对象并存储在数组students中。

6. 答卷信息的解析

  • 将输入的答卷数据按行分割,得到parts_S数组。
  • 遍历parts_S数组,每行代表一份答卷。
  • 对于每份答卷,提取试卷号、学号,并创建AnswerSheet对象。
  • 遍历答卷中的答案,对于每个答案,提取答案内容,并将其添加到答卷中。
  • 如果答卷中的题目编号与试卷中的题目顺序不匹配,则在答卷中添加空答案。
  • 遍历学生数组,查找对应的学生,如果找到,则将答卷设置为该学生的答卷。
  • 如果没有找到对应的学生,则创建一个新的学生对象,并将答卷设置为其答卷。
复制代码
for (int j = 2; j < S.length; j+=2) {//答卷上有答案的题目数量
                    if(correspondingExam!=null) {
                        for (int k = 0; k < correspondingExam.getAllQuestions().size(); k++) {
                            if (!S[j].trim().isEmpty()) {
                                if (k + 1 == Integer.parseInt(S[j])) {
                                    if(j+1>=S.length)
                                        answerSheets[i].changeAnswer(" ", k+1 );
                                    else
                                        answerSheets[i].changeAnswer(S[j+1].trim(), k+1 );
                                } else {
                                    if (!answerSheets[i].getAnswers().isEmpty()) {
                                    } else {
                                        answerSheets[i].changeAnswer(" ", k+1 );
                                        //如果这个位置没有被赋值,也就是不为空
                                    }
                                }
                            }
                        }
                    }
                }
复制代码

7. 删除题目信息的解析

  • 分割输入字符串,获取要删除的题目编号。
  • 遍历所有试卷。
  • 对于每个试卷,遍历所有要删除的题目编号。
  • 在试卷中查找并删除指定编号的题目。
复制代码
// 删除题目  不是真的删除,把被删除的题目赋值为null
    public boolean deleteQuestionById(int id) {
        for (int i = 0; i < questions.size(); i++) {
            if (questions.get(i).getId() == id) {
                //questions.remove(i); 
                questions.get(i).setContent("del_null");
                questions.get(i).setScore(0);
                return true; // 返回成功
            }
        }
        return false; // 如果没有找到对应的题目,返回失败
    }
复制代码

8. 输出结果

      • 检查答卷是否关联了有效的试卷: 对于每张答卷,首先检查它是否关联了一个有效的试卷(a.getExam() != null)。如果试卷不存在,则输出提示信息 "The test paper number does not exist"

      • 检查试卷是否有题目: 如果试卷存在但试卷中没有题目(a.getExam().getAllQuestions().isEmpty()),则输出当前答卷关联的学生信息,但不包含答案和评分。

      • 寻找答卷对应的学生并输出结果: 如果试卷存在且有题目,则进一步检查是否有学生与这张答卷关联。这是通过遍历 students 数组来完成的。对于每个学生,检查其答卷是否与当前遍历的答卷 a 相等。

      • 如果找到了对应的学生,并且学生的名字不是默认的 "stu_null",则调用 a.CalAndJudge() 方法来计算分数,并调用 a.printf_result() 和 s.printStudents() 方法来输出学生信息和答卷结果;如果学生的名字是 "stu_null",表示学生信息不完整,仍然计算分数和输出答卷结果,会额外输出 "not found" 提示。

三、踩坑心得

对源码的提交过程中出现的问题及心得进行总结,务必做到详实,拿数据、源码及测试结果说话,切忌假大空

针对第二次大作业最后一题:

对于第二次大作业的最后一题,在拆解试卷信息的parseExam()函数中,遍历题库去根据试卷顺序添加题目和设置题目对应的分数时,不能直接像下面这样:

复制代码
for (Question q : questions) {
    if (q.getId() == Ques_id) {
        Question temp_q = new Question(); 
        temp_q = q;  // 这里只是让temp_q指向q所指向的对象,没有创建新对象
        exams[i].addQuestion(temp_q);
        temp_q.setScore(Ques_score);
        break;
    }
}
复制代码

正确的做法是创建一个新的 `Question` 对象,这样每个试卷都有自己的题目对象,可以独立地设置分值:

for (Question q : questions) {
    if (q.getId() == Ques_id) {
        Question temp_q = new Question(q);  // 正确创建q的一个副本
        exams[i].addQuestion(temp_q);
        temp_q.setScore(Ques_score);
        break;
    }
}

针对第三次大作业最后一题:

1.正则表达式不够准确,没有考虑到 "#S:1 20201103 #A:1-5 #A:2-2 #B:3"这种情况下,是正确的格式,且"2 #B:3"是一个整体。

 因此对个字符串解析的正则表达式做出相应的修改:

 修改了之后:

 2.没有考虑到试卷、问题、答卷是否为空,导致单信息输入非零返回。

 对此添加相应判断:

3.没有考虑到答案为空字符的情况,空字符也算一个答案,空用户答案和空标准答案匹配应为true

 

四、改进建议

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

  1. 在动手写代码之前,一定要先多读几遍题目内容,注意细节,尤其是针对各种情况要综合考虑,以免出现不理清思路就开始敲,敲到后面发现自己不是漏了这一个细节,就是忘记考虑另一种情况,还要再反复修改。
  2. 而且编程序的时候,一定要边敲边写注释,尽管当时你的思路是清晰的,可是等到下一次进阶版大作业时,你想从原来的代码上去修改,就很费事了,因为,,已经逐渐看不懂自己敲得是什么了。
  3. 还有就是实现代码功能的时候,对于复杂功能的实现,尽量将其拆分成多个更小的函数去实现,这样既可以方便阅读以及其他相关功能调用函数,还有助于代码的维护性,不然下一次大作业要对该功能进行修改,就比较费时费力了,
  4. 由于题目输出的判断情况较多,且输出语句内容关乎到题目、答卷的等先后顺序,这一块比较容易混乱,写着写着就忘记了下一个判断情况要写什么,所以要先理清这一块的思路,最好先将其记录、写下来,这样敲代码的时候方便直接对照着。

五、总结

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

  通过完成这三次答题判题程序的编程作业,我不仅加深了对Java编程的理解,而且对面向对象编程中的类的应用有了更加清晰的认识。这些练习让我体会到了编程不仅仅是写代码,更是一个涉及规划、设计和优化的全面过程。

  学到了在对题目进行编程之前,要先弄明白题目的目的,最好是把相关类图给画出来,方便后续写代码思路更清晰。

  然后,我体会到了代码重构的重要性。在实现复杂功能时,将大的代码块分解成更小、更易于管理的函数。这样做不仅使得代码更加模块化,也提高了代码的可读性和可维护性。此外,通过消除重复代码,也能够更快地进行后期修改,并能够减少引入新bug的风险。

  此外,我也认识到了测试的重要性,除去题目给的测试样例,还应该去编写其他不同情况下的测试样例,通过编写样例去测试,能够更好的找到程序的错误和不足之处。

 

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