Title

java题目集1-3总结

一、前言

概括:经过了三次java的大作业的练习,也算是入门java了。这三次大作业的难度是层层递进的,虽然一次比一次难,但是每一次大作业都是基于前面大作业的知识点。所以每一次大作业认真完成,并认真总结知识点,多花点时间,大作业还是勉强可以完成。

1.知识点:

大作业1:大作业1主要是类的设计,每个题目都是有关类设计与对象的使用。刚开始学java,理解类和对象的概念是十分重要的,这对以后写长、复杂代码十分有帮助。每道题目都要求设计一个或多个类,如 Fan、Student、Score、Question、ExamPaper 和 AnswerSheet。这些类的设计体现了面向对象编程的基本思想封装。通过将数据(属性)和操作这些数据的方法(行为)捆绑在一起,对外部世界隐藏内部实现细节,只暴露必要的接口。例如大作业第四题,创建成绩类,在类中写了getPinshi()和getqimo()方法来获取私有成员期末和平时成绩。通过封装,将数据和操作这些数据的方法写在一个类中,当我们需要进行相应操作时,直接调用类中的方法,实现我们想要的功能。

大作业2:大作业2在大作业1的基础上增加了一些知识点,比如Comparable接口,重写compareTo方法,List 接口,Map 接口。大部分都是有关Java集合框架,通过这些接口存储和操作集合数据。

大作业3:大作业三增加了日期类的使用,通过LocalDate类的使用,来获取日期是当年第几天、当月第几天、当周第几天;最后一题在上一次大作业的基础上,增加了删除题目要求,进一步考察了集合类的方法使用。

2.题量与难度

这三次大作业题量都差不多,但难度是一次比一次大。不过每一次大作业都是基于以前大作业的知识点,进一步增加新的知识点。使用了更多的类及其方法,要从网上查找资料,了解相关类的使用。

二、设计与分析

1.第一次大作业

概括:第一次相对来说比较简单,注重类的设计,主要是为了让我们从面向过程的思维过渡到面向对象的思维。

第一题

设计一个风扇类,在风扇类中定义了风扇速度,打开状态,尺寸私有域数据以及相应操作方法

第二题

类与对象的使用,增加了无参,有参构造方法。在主类中创建学生类的对象,并调用它的方法

第三题

数组类的使用。在主类当中声明了对象数组,在数组中存放每一个对象。

第四题

在第三题的基础上增加了关联类的使用,创建了成绩类和学生类,在学生类中定义了三个成绩类私有域数据,学生类关联了成绩类

在学生类当中有语文,数学,物理这三个成绩类属性,在学生类当中有计算平均成绩,总分成绩的方法(行为),在这些方法中都调用了成绩类的方法,用来获取每个成绩的平时与期末成绩,例如this.yuwen.getqimo(),shuxue.getgrade()等等
最后在主类当中声明学生类数组,用来存放每一个学生对象,依次输入3个学生的每门课成绩,创建学生对象后放入对象数组中。

第五题:

第五题是本次大作业的综合题,也是最难的一道题。此题目是设计实现答题程序,模拟一个小型的测试,要求输入题目信息和答题信息,根据输入题目信息中的标准答案判断答题的结果。
类图:

根据题目要求,首先输入的是题目数量,然后是题目内容,答题信息,最后以end结束;输出结果是判题信息。为了实现这个简单的考试系统,定义了题目(Topic 类)、试卷(testpaper 类)以及创建试卷和输出结果的功能。下面是代码的分析以及一些解释和心得:

Topic 类

Topic 类表示一个具体的题目,包含题目编号(identifier)、题目内容(itemContent)和标准答案(standardAnswer)。它提供了一些基本的操作,比如设置和获取题目编号、内容和答案,还有一个方法 Testquestion1 用于检查用户输入的答案是否正确。

点击查看代码
class Topic{
    private int identifier;//题目编号
    private String itemContent;
    private String standardAnswer;
    public void setNumber(int number) {
        this.identifier = number;
    }
    public int getNumber() {
        return identifier;
    }
    public void setItemContent(String itemContent) {
        this.itemContent = itemContent;
    }
    public String getItemContent() {
        return itemContent;
    }
    public void setStandardAnswer(String standardAnswer) {
        this.standardAnswer = standardAnswer;
    }
    public String getStandardAnswer() {
        return standardAnswer;
    }
    public boolean Testquestion1(String Answer)
    {
        if(Objects.equals(Answer, standardAnswer))
            return true;
        else return false;
    }
    public Topic(int identifier, String itemContent, String standardAnswer){
        this.identifier = identifier;
        this.itemContent = itemContent;
        this.standardAnswer = standardAnswer;
    }
}
成员变量

identifier:表示题目的编号,是一个整型变量。
itemContent:表示题目的具体内容,是一个字符串。
standardAnswer:表示题目的标准答案,也是一个字符串。

构造函数

Topic(int identifier, String itemContent, String standardAnswer):构造函数接受三个参数,分别是题目编号、题目内容和标准答案,并初始化类的相应成员变量。

方法

setNumber(int number):设置题目的编号。
getNumber():返回题目的编号。
setItemContent(String itemContent):设置题目的内容。
getItemContent():返回题目的内容。
setStandardAnswer(String standardAnswer):设置题目的标准答案。
getStandardAnswer():返回题目的标准答案。
Testquestion1(String Answer):检查用户提供的答案 Answer 是否与标准答案相等,返回布尔值表示答案是否正确。
testpaper 类代表一张试卷,包含题目数量(number)、题目数组(topics)、用户答案数组(answers)以及一个用于存储每个题目的回答正确与否的数组(questions)。它提供了一个方法 Testquestion2 用来检查所有题目的答案是否正确,并将结果存储在 questions 数组中。

Create 类

Create 类负责创建 testpaper 对象。它通过读取用户的输入来构建试卷,输入格式为带有标签(如 #N:、#Q: 和 #A:)的字符串,标签分别代表题目编号、题目内容和标准答案。此外,还读取了用户为每个题目提供的答案。

PrintfIO 类

PrintfIO 类负责输出试卷的信息,包括题目内容和用户的答案,以及最终的正确与否的结果。

代码解释

输入处理:在 Create.setTestpaper 方法中,通过正则表达式从输入字符串中提取题目编号、题目内容和标准答案,并使用这些信息创建 Topic 对象。同时,通过正则表达式从另一行输入中提取用户的答案。

点击查看代码
for(int i=0;i<number;i++) 
        {
            String input = sc.nextLine();

            //String input = "#N:1 #Q:1+1= #A:2";
            // 正则表达式匹配 #N: 后面的数字
            // 正则表达式匹配 #N: 后面的数字
            String nRegex = "#N:\\s*(\\d+)";
            // 正则表达式匹配 #Q: 后面的内容(假设我们想要整个问题)
            String qRegex = "#Q:\\s*([^#]+?)\\s*(?=#|$)";
            // 正则表达式匹配 #A: 后面的数字
            String aRegex = "#A:\\s*([^\\s]+)";

            // 创建Pattern对象
            Pattern nPattern = Pattern.compile(nRegex);
            Pattern qPattern = Pattern.compile(qRegex);
            Pattern aPattern = Pattern.compile(aRegex);

            // 创建Matcher对象并查找匹配项
            Matcher nMatcher = nPattern.matcher(input);
            Matcher qMatcher = qPattern.matcher(input);
            Matcher aMatcher = aPattern.matcher(input);

            // 提取信息
            if (nMatcher.find()) {
                 identifier= Integer.parseInt(nMatcher.group(1)); // 输出 1
            }
            if (qMatcher.find()) {
                 itemContent =qMatcher.group(1); // 输出 5 +5=
            }
            if (aMatcher.find()) {
                 standardAnswer =aMatcher.group(1); // 输出 10
            }
            topic[--identifier] = new Topic(identifier, itemContent, standardAnswer);
        }
        String input=sc.nextLine();
        // 正则表达式匹配"#A:"后跟一个或多个数字
        Pattern pattern = Pattern.compile("#A:\\s*([^\\s]+)");
        Matcher matcher = pattern.matcher(input);
        int i=0;
        while (matcher.find()) {
            // group(1) 表示第一个括号匹配的内容,即数字部分
            answers[i++] = matcher.group(1);
        }
        testpaper=new testpaper(number, topic, answers);
        String input1=sc.nextLine();
        return testpaper;
    }
为了按输入格式正确输入题目和答卷信息,故运用正则表达式用于从输入字符串中提取题目编号、题目内容和标准答案,且能确保输入数据格式正确,从而能从输入数据正确提取相关信息。正则表达式主要用于从输入字符串中提取题目编号、题目内容和标准答案。

试卷创建:根据题目数量和创建好的 Topic 对象,以及用户的答案,创建 testpaper 对象。在tespaper对象中创建了题目对象数组用于存放创建好的题目,创建了答题答案数组,存放每个所作答案。由于多个题目,题号顺序与输入顺序可能不同。例如:

2
#N:2 #Q:1+1= #A:2
#N:1 #Q:5+5= #A:10
#A:10 #A:2
end

第二题先输入,第一题后输入,那么在题目数组中第二题作为数组中第一个元素,第一题作为第二个元素;那么在所作答案数组,第一个数组元素是第一题的答案,第二个数组元素是第二题的答案,如果根据数组下标进行匹配的话,会导致题目与所作答案不匹配;为了解决这个问题在输入题目信息中,提取题目id并减一(数组下标从0开始)作为数组下标,这样数组下标就与题目信息匹配了(第一个数组就是第一题,第二个数组元素就是第二题)

输出处理:在 PrintfIO.printf 方法中,输出每个题目的内容和用户答案,并输出每个题目的回答正确与否的结果。根据数组下标对应每道题信息,每道题的答案,每道题的判题结果

2.第2次大作业

注:第2次大作业前面三道题与第一次大作业前面的题目类似,在此就不做赘述了。主要分析最后一道题

最后一道也是答题判题程序,但在上一次的基础上,增加了试卷信息,试卷总分警示(当试卷总分值小于100时提示消息)。输入格式,输出格式都有所变化。

下面我将按类进行分析

Question 类

这个类主要处理题目信息。为了使每个题目id指向相依的题目(可根据题目id,就可获取到相依的题目),故使用Map集合类,通过唯一的键来存储和检索值。

private Map<Integer, String[]> questions = new HashMap<>();
//HashMap 的键是一个整数(问题ID)
public void addQuestion(int id, String content, String answer) {
questions.put(id, new String[]{content, answer});
}
//根据键值(问题ID)返回问题内容
public String getContent(int id) {
return questions.getOrDefault(id, new String[]{""})[0];
}
//根据键值(问题ID)返回答案
public String getAnswer(int id) {
return questions.getOrDefault(id, new String[]{""})[1];
}

在Question类中,使用了HashMap,其中键为整型(问题ID),值为一个字符串数组,该数组的第一个元素是问题内容,第二个元素是问题的标准答案。然后通过addQuestion方法,添加一个问题到 questions 映射表中。使用 getOrDefault 方法来获取问题内容。如果没有找到对应的键,则返回默认值 ""。然后返回数组中的第一个元素(问题内容)。checkAnswer方法检查用户提供的答案是否与标准答案相同,调用 getAnswer 方法获取标准答案,并使用 equals 方法比较用户提供的答案与标准答案是否相等。

Paper类

这个试卷类主要用来存放每一张试卷信息,输入格式为一行为一张试卷,可输入多行数据。为了存放每一张试卷,用hashmap集合来存储每一张试卷信息(paperid,questionid,sore);键为整型(试卷ID),值为另一个 Map,该内部 Map 的键为整型(题目ID),值为整型(分数)。这样的结构非常适合用来存储一个试卷中的所有题目及其对应的分数。
在这个类中使用了addPaper(int paperId, int questionId, int score),向指定的试卷添加一个题目及其分数;getTotalScore(int paperId)方法,计算并返回指定试卷的总分

AnswerSheet类

这个类用来表示一张答题纸,包含试卷ID和对应的问题答案列表。提供了增删查改答题纸的方法。
使用Map集合,试卷id指向每一个答案列表。通过paperid获取相依试卷答案,在HashMap存储的是答案列表(类型:List)。

Input类

该类负责处理从标准输入读取的数据,并根据数据的不同格式将其传递给相应的处理类。

方法解析
  1. input()
    功能:从标准输入读取数据,并根据输入的格式调用相应的处理方法。
    实现:使用 Scanner 类从标准输入读取每一行数据,并根据每一行数据的前缀(#N:、#T:、#S:)调用不同的处理方法。
    输入处理逻辑
    读取输入行:使用 scanner.nextLine() 方法读取每一行输入。
    结束条件:当输入行等于 "end" 时停止读取。
    处理逻辑:
    如果输入行以 "#N:" 开头,则调用 createQuestion 的 processQuestion 方法处理该行数据。
    如果输入行以 "#T:" 开头,则调用 createPaper 的 processPaper 方法处理该行数据。
    如果输入行以 "#S:" 开头,则调用 createAnswerSheet 的 processAnswerSheet 方法处理该行数据。
    关闭扫描器:在完成所有输入处理后,关闭 Scanner 对象。
  2. getAnswerSheet(), getPaper(), getQuestion()
    功能:提供访问器方法来获取处理后的问题、试卷和答题纸对象。
    实现:返回相应的处理类中的对象实例。
Output类

这个类是整段代码最核心的部分。该类负责处理从 Input 类获取的数据,并打印出处理结果。

下面是对 Output 类的详细解析:

成员变量
input:一个 Input 类的对象实例,用于获取处理过的问题、试卷和答题纸对象。

方法解析
  1. setInput(Input input)
    功能:设置 Output 类使用的 Input 对象。
    参数:Input 类的对象实例。
    实现:将传入的 Input 对象赋值给成员变量 input。
  2. printResults()
    功能:打印处理后的结果,包括问题内容、用户答案、正确性以及总分。
    实现:遍历 AnswerSheet 类中的答题纸,使用 Paper 和 Question 类的相关方法获取题目信息和答案,并输出结果。
输出处理逻辑

遍历答题纸:遍历从 Input 类获取的 AnswerSheet 对象中的答题纸列表。

验证试卷编号:检查当前试卷编号是否存在于 Paper 对象中,如果不存在,则输出错误信息并终止当前试卷的处理。

获取试卷信息:从 Paper 对象中获取当前试卷的所有题目及其分数。

保持题目顺序:使用 LinkedHashSet 保存题目ID,确保题目按原始顺序输出。

处理每份答题纸:
1.初始化四个列表:questionContents 用于保存题目内容,results 用于保存每个题目的回答是否正确,scores 用于保存每个题目的得分,answers 则是当前答题纸的答案列表。
2.遍历题目ID,并根据题目ID获取题目内容、用户答案及正确答案,然后计算每个题目的得分,并将相关信息存入各自的列表中。

输出题目及答案:遍历题目内容列表,输出每个题目的内容、用户的答案以及回答是否正确。

输出总分:计算所有题目的得分总和,并输出总分。

测试:

由于试卷题目的顺序与题号不一致。例如:
试卷题目的顺序与题号不一致。例如:

N:1 #Q:1+1= #A:2
N:2 #Q:2+2= #A:4
T:1 2-70 1-30
N:3 #Q:3+2= #A:5
S:1 #A:5 #A:22

可以看到试卷中首先是题号为2的信息,再是题号为1的信息。答卷不用说了,答案顺序是根据试卷作答,第一个答案是题号为2的答案,第二个答案为题号为1的答案

为了保证问题的顺序,我使用了LinkedHashSet 保持问题的顺序LinkedHashSet<Integer> questionIds = new LinkedHashSet<>(paper.keySet());这样就可以根据试卷中的题目id顺序作答

3.第3次大作业

第二题:jmu-java-日期类的基本使用

题目要求:

1.给定一个日期,判定是否为合法日期。如果合法,判断该年是否闰年,该日期是当年第几天、当月第几天、当周第几天。
2.给定起始日期与结束日期,判定日期是否合法且结束日期是否早于起始日期。如果均合法,输出结束日期与起始日期之间的相差的天数、月数、念书。

判断一个日期是否合法,该日期是当年第几天、当月第几天、当周第几天,如果自己写代码解决的话,会有点复杂。但Java已经帮我们封装好了方法。通过Java.Date类的使用,高效且简单的解决此问题

Date类
成员变量

year, month, day:表示日期的年、月、日。
dayOfYear, dayOfMonth, dayOfWeekInNumber:表示一年中的第几天、一月中的第几天以及一周中的第几天(数字表示)。
valid:标记日期是否有效。

方法

构造函数 Date()用来初始化日期对象。

date_judge()判断日期是否有效,并计算日期的一年中的第几天、一月中的第几天以及一周中的第几天。使用 LocalDate.parse 方法尝试解析格式化的日期字符串,如果成功则设置相关属性并标记日期为有效,否则标记为无效。

formatDateString(int year, int month, int day)用来格式化日期字符串。返回格式化的日期字符串(形如 "YYYY-MM-DD")。

Valid类
成员

year, month, day:用于存储日期差异计算的结果。

方法

isValidDate(String dateInput)方法检查输入的日期字符串是否有效。使用 LocalDate.parse 尝试解析日期字符串,如果成功则返回 true,否则返回 false。

is_LeapYear(int year)用来检查给定年份是否为闰年。根据闰年的定义进行判断。

data_apart(Date data1, Date data2)计算两个日期之间的差异。使用 LocalDate.of 创建两个 LocalDate 实例,并使用 ChronoUnit.DAYS.between 计算两日期间的天数差。

valid_DX(Date data1, Date data2)方法用来检查第一个日期是否早于第二个日期。使用 LocalDate.of 创建两个 LocalDate 实例,并比较它们

附:

本题使用日期类来解决相关问题,刚开始我是不知道有此方法的。我看第二题有点复杂,想着看看Java中是否有相关的类可以帮助解决,从网上查找资料,找到Java.time相关类

通过LocalDate类的使用,判定是否为合法日期,如果合法,使用:

  • getDayOfYear():返回该日期是一年中的第几天。
  • getDayOfMonth():返回该日期是一个月中第几天。
  • getDayOfWeek():返回该日期对应的星期几。

通过DateTimeFormatter用于定义日期和时间的格式化模式,并且可以根据这种模式来格式化日期时间或解析日期时间字符串。

第三题:

又是答题判题程序!!!哭死!!!已经被上两次作业中的答题判题程序折磨的.....(欲哭无泪了^ _ ^)这次的答题判题程序太变态了,增加了学生信息、删除题目信息😔但欣慰的是逻辑上还是基于第一次,第二次答题判题程序的逻辑,增加了一部分要求😊,只需在原有的代码基础上修改即可
这次答题判题程序3使用到的知识点与前一次相同,也用到了Map接口(我发现使用Map键值对的存储方式,查找数据是真的方便高效,通过键来查找所标识的值)

下面来进行所写代码分析:


Question 类
  • 功能:
    存储问题的基本信息,包括问题ID、内容和答案。
    提供方法检查答案是否正确。
  • 方法:
构造函数 初始化问题ID、内容和答案。
getContent() 返回问题内容
getId() 返回问题ID。
getAnswer() 返回问题答案
checkAnswer(String givenAnswer) 检查给定的答案是否正确。
QuestionBank 类
  • 功能:
    存储一系列问题,并通过问题ID进行索引。
    支持添加、删除、查询问题等功能。
  • 方法:
成员变量 使用 LinkedHashMap 来存储问题,以保持插入顺序
addQuestion(Question question) 向题库中添加一个问题
getQuestionById(int id) 根据问题ID获取问题
containsQuestion(int id) 检查题库中是否存在某个问题ID
deleteQuestion(int id) 删除某个问题

Map 接口
使用了 Map 接口提供的方法,如 put、get、containsKey 和 remove,来操作问题对象。
泛型
Map<Integer, Question> 使用了泛型来指定键和值的类型,这有助于类型安全。
集合框架
使用了 Java 集合框架中的 Map 接口和 LinkedHashMap 实现类。

Paper 类
  • 功能:
    表示一份试卷,包含多个问题及其对应的分数。
    支持添加、查询试卷信息,计算试卷总分等功能。
  • 方法:
成员变量:使用 HashMap 来存储试卷信息,其中键是试卷ID,值是一个 Map,该 Map 的键是问题ID,值是分数。
addPaper(int paperId, int questionId, int score):向试卷中添加一个问题及其分数。
getPaper(int paperId):根据试卷ID获取试卷信息。
getTotalScore(int paperId):计算给定试卷的总分。
containsPaper(int paperId):检查是否存在某个试卷ID。
getAllQuestionIds(int paperId):获取试卷上的所有题目ID。
getScoreForQuestion(int paperId, int questionId):根据试卷ID和题目ID获取相应的分数。

输入格式:试卷信息为独行输入,一行为一张试卷,多张卷可分多行输入数据。

为了使每一张试卷id映射唯一 一张试卷,故使用Map<Integer, Map<Integer, Integer>> 使用嵌套映射(nested maps)的方式来组织数据的结构,外层 Map 的键是试卷的 ID (paperId),值是一个内层的 Map,这个内层 Map 的键是问题的 ID (questionId),值是问题的分数 (score)。这样可通过试卷id可获取该试卷上的所有题目的分数。

Studentinformation 类
  • 功能:
    存储学生的学号及其对应的姓名。
    支持添加、查询学生信息等功能。
  • 方法:
成员变量:使用 HashMap 来存储学生的学号及其姓名。
addStudent(String id, String name):向学生信息表中添加一条记录。
getName(String id):根据学号获取学生的姓名。
containsStudent(String id):检查学生信息表中是否存在某个学号
AnswerPaper 类
  • 功能:
    存储学生的答卷信息,包括试卷ID、学生学号及其回答的问题列表。
    支持添加、查询学生答卷信息等功能。
  • 方法:
成员变量:使用 HashMap 来存储学生的答卷信息,其中键是试卷ID,值是一个 Map,该 Map 的键是学生的学号,值是一个问题答案列表。
addAnswerSheet(int paperId, String studentId, List answers):向答卷记录中添加一个学生的答卷。
getAnswerSheet(int paperId):根据试卷ID获取所有学生的答卷记录。
嵌套类:Answer 用于表示单个答案,包含题目序号和答案内容。

final Map<Integer, Map<String, List<Answer>>> answerSheets = new HashMap<>();
这个成员变量是一个三层嵌套的 Map 结构:
第一层 Map 的键是 int 类型,表示试卷的 ID (paperId)。
第二层 Map 的键是 String 类型,表示学生的 ID (studentId)。
第二层 Map 的值是 List 类型,表示该学生在特定试卷下的答案列表。

DeletionQuestion 类
  • 功能:
    记录已被删除的问题ID。
    支持标记删除问题和检查问题是否已被删除。
  • 方法:
成员变量:使用 HashSet 来存储已删除的问题ID。
markDeleted(int questionId):标记某个问题已被删除。
isDeleted(int questionId):检查某个问题是否已被删除。

private final Set<Integer> deletedQuestions = new HashSet<>();
这个成员变量是一个 Set,用于存储已被删除的问题 ID。
使用 HashSet 作为实现类,这是因为 HashSet 提供了平均时间复杂度为 O(1) 的快速查找、添加和删除操作。
deletedQuestions 是 final 类型的,这意味着集合本身不能被重新赋值,但集合内的元素是可以被添加和删除的。

输入处理 (Input 类)
  • 功能:
    处理从标准输入读取的数据行,并根据行的前缀调用相应的方法来处理这些行。
  • 方法:
构造函数:接收题库、试卷、学生信息、答卷以及删除题目的实例。
validateInputFormat_1(String line):验证题目输入行的格式是否正确。
validateInputFormat_2(String line):验证试卷输入行的格式是否正确。
handleInput(String line):根据输入行的前缀处理不同的输入类型。
  • 输入行处理逻辑:
题目输入:以#N:开头,格式为#N:问题ID #Q:问题内容 #A:答案。
试卷输入:以#T:开头,格式为#T:试卷ID 问题ID-分数 问题ID-分数 ...,并检查试卷总分是否为100。
学生信息输入:以#X:开头,格式为#X:学号-姓名 学号-姓名 ...。
答卷输入:以#S:开头,格式为#S:试卷ID 学号 #A:问题序号-答案内容 #A:问题序号-答案内容 ...。
删除题目输入:以#D:N-开头,格式为#D:N-问题ID。
输入类中正则表达式部分的详细解析

使用正则表达是为了验证输入行(题目信息,试卷信息行)的格式是否符合预期的标准,符合预期则返回true,不符合则返回false且输入的该行无效丢弃,不做输入处理。
validateInputFormat_1
用于验证题目输入行的格式。
确保输入行以 #N: 开头,并且包含题目 ID、问题内容(包含数字加法运算)和答案。
validateInputFormat_2
用于验证试卷输入行的格式。
确保输入行以 #T: 开头,并且包含试卷 ID 和一系列问题及其分数。

点击查看代码
public static boolean validateInputFormat_1(String line) {
    // 正则表达式用来验证输入行的格式
    String regex = "^#N:\\d+(\\s+#Q:\\d+\\+\\d+=\\s+#A:\\d+)$";
    return line.matches(regex);
}
格式说明:

^#N::行开始位置 ^ 后跟 #N:,表示该行为题目信息。
\d+:一个或多个数字,表示题目 ID。
(\s+#Q:\d+\+\d+=\s+#A:\d+):空格 \s+,后跟 #Q: 和一个或多个数字,表示问题内容中包含数字加法运算,然后是 =,接着是空格 \s+ 和 #A:,最后是一个或多个数字,表示答案。
$:行结束位置。

点击查看代码
public static boolean validateInputFormat_2(String line) {
    // 正则表达式用来验证输入行的格式
    String regex = "^#T:\\d+(\\s+\\d+-\\d+)+$";
    return line.matches(regex);
}
格式说明:

^#T::行开始位置 ^ 后跟 #T:,表示该行为试卷信息。
\d+:一个或多个数字,表示试卷 ID。
(\s+\d+-\d+)+:空格 \s+,后跟一个或多个数字 - 一个或多个数字,表示问题及其分数,这样的组合出现一次或多次 +。
$:行结束位置。

输出处理 (Output 类)
  • 功能:
    根据已处理的数据输出考试结果。
  • 方法:
构造函数:接收题库、试卷、学生信息、答卷以及删除题目的实例。
processOutput():遍历所有答卷记录,输出每个学生的得分情况。

这段代码是整段代码中最核心的部分,用于处理输出逻辑,包括根据学生的答卷情况计算成绩,并输出最终的成绩信息。
processOutput()方法处理过程:
1.遍历答卷:

  • 遍历 answerPaper 中的所有答卷记录。
  • 每个记录是一个键值对,键是试卷 ID (paperId),值是一个映射关系,键是学生 ID (studentId),值是该学生的答案列表。

2.检查试卷是否存在:

  • 使用 paper.containsPaper(paperId) 方法检查是否存在给定的试卷 ID。

3.处理每个学生的答案:

  • 遍历每个学生的答案列表。
  • 获取学生 ID 和名字。
  • 计算总分,并构建分数字符串。

4.处理每个问题的答案:

  • 获取试卷上的所有问题 ID 序列。
  • 对每个问题进行处理:
    • 检查学生的答案。
    • 检查问题是否已被删除。
    • 检查问题是否存在。
    • 判断答案是否正确,并计算分数。
    • 输出问题内容、答案内容及是否正确,并累加分数。

5.输出最终成绩信息:

  • 如果学生的名字存在,则输出学生 ID、名字、各题得分和总分。
  • 如果学生的名字不存在,则输出学生 ID 未找到的消息。

主类 (Main 类)

  • 功能:
    读取标准输入,并根据输入处理逻辑进行处理。
    处理完成后输出结果。
  • 方法:
    主函数:从标准输入读取数据直到遇到“end”,然后调用输出处理方法。
  • 输入输出流程:
读取输入:从标准输入读取数据行。
处理输入:根据输入行的前缀调用相应的处理方法。
题目处理:将题目添加到题库中。
试卷处理:将问题及其分数添加到试卷中,并检查总分是否为100。
学生信息处理:将学生信息添加到学生信息表中。
答卷处理:将学生的答案添加到答卷记录中。
删除题目处理:标记题目为已删除。
输出处理:遍历所有答卷记录,并输出每个学生的得分情况。

三、采坑心得

1.写了这么多的题目,我发现输入格式和输出格式是最头疼的地方。往往写完之后,把测试样例一输入,可能会出现越界问题IndexOutOfBoundsException,又或是正则表达式匹配不正确,报错。这样的问题,只能一步步进行调试,代码处理输入行时,可能存在错误或遗漏。然后在进一步改进正则表达式。写好一个正则表达式真的很重要,它可以帮你判断很多东西,来验证输入的数据是否符合预期的格式,还可用获取匹配的子字符串等。这三次大作业中几乎每一题都用到了正则表达式,可见正则表达式的重要性。

2.在答题判题程序-1中,输入行中可能会有多余的空格输入,例如:

1
#N:1 #Q: The starting point of the Long March is #A:ruijin
#A:ruijin
end

由于我用的是正则表达式匹配,"#Q:\s([^#]+?)\s(?=#|$)";将匹配出" The starting point of the Long March is "(首尾都有空格),但是输出结果是没有空格的。输出题目信息那一行开头空格我愣是没有发现,
我就奇怪了明明最后答案和PTA答案实是一样的,为什么测试点没有通过。也怪我眼瞎😔,那么大一个空格愣是没有发现,最后在同学的帮助下找到原因(也是服了自己😀)。我在输出结果中,用了trim()方法,可以帮助我去除掉字符串中首尾空格

3.答题判题程序-3中有一个问题困扰了我好久。如果答案输出时,一道题目出现答案不存在、引用错误题号、题目被删除,提示信息怎么输出;如果答案输出时,一道题目同时出现答案不存在、引用错误题号、题目被删除,怎么让答案不存在的优先级最高。刚开始我遍历的是答卷上的题目顺序号,但题目顺序号并不是题目id号,当出现题目删除和题目本身不存在这两种情况都存在时,答案不存在和题目被删除提示消息无法判断到底是哪种情况;故遍历试卷上的题目id序列,在for循环中按输入顺序遍历每一个题目id,当出现这三种情况之一,则跳出循环,该题目不进行处理。判断出现答案不存在的情况写在最前面,保证此优先级最高。

for (int questionId : questionSequence)
{
  .......//省略代码
  if (answerContent.isEmpty()) {//没有作答(优先级最高)
      System.out.println("answer is null");
      scoreBuilder.append("0 ");
      continue;
  }
  if (deleterQuestion.isDeleted(questionId)) {//题目是否删除,被删除则跳过
      System.out.println("the question " + questionId + " invalid~0");
      scoreBuilder.append("0 ");
      continue;
  }
  if (question == null) {//试卷中出现不存在的题目
      System.out.println("non-existent question~0");
      scoreBuilder.append("0 ");
      continue;
  }
.......
}

四、改进建议

1.这三次大作业类的设计至关重要,一个好的类设计,可以让代码可读性更强,逻辑上更加健壮。第一次大作业没有很好的进行类的设计,代码看起来有点乱。在后面几次作业题目中,先进行类的设计,然后根据类图写代码

2.代码中还是使用到了很多for,ifelse语句。过多的 if-else 语句会导致嵌套层次加深,使得代码难以阅读和理解。产生过多的分支,可能会导致一些潜在的问题,增加了逻辑的复杂度,从而提高了引入错误的风险。在今后写代码中,应尽量较少for循环,if-else语句的使用

3.这三次大作用使用到了大量的正则表达式匹配,有的复杂一点的匹配算法是借助ai完成的,运用正则表达式并不熟练。在课后,需要去更多的了解有关正则的方式匹配。

五、总结

这三次大作业除了但三次大作业最后一题没有完全做出来,其他的都成功完成了。虽然每次大作业花费时间比较长(精力付出最多的一门课程),但每次大作业的顺利完成,看到一个个测试点通过,心里觉得还是值得的(没有白瞎这么多时间(_))。

这三次大作业学到的东西还是蛮多的,学会运用正则表达式匹配字符串,使用集合类存储、查询数据(例如:HashMap用于存储问题、试卷和答案,提供了灵活的数据组织方式;List用于存储答案列表;Set用于保持问题的顺序)

深入理解了Java继承与封装的思想。通过类的定义实现了数据的封装,如 createQuestion, createPaper, createAnswerSheet 等类的定义。Java面向对象编程思想进一步加深。

在写代码过程中,多次使用到了调试。代码调试也至关重要,在软件开发过程中非常重要的一个环节。它可以帮助开发者找出程序中的错误,理解程序的实际运行情况,并优化代码。会调试,在编程中可以起到事半功倍的效果,学习调试工具和技巧是提高编程效率和代码质量的关键步骤。经过这三次大作业的练习,我的代码调试能力进一步加强,写代码效率也不断提高。

总而言之,Java还是刚刚入门而已。学习 Java 是一个长期的过程,需要不断地积累和实践。要学的东西还有很多,慢慢积累吧!💪

posted @ 2024-10-25 16:53  于龙辉  阅读(122)  评论(2)    收藏  举报