jk-jk-123

导航

PTA大作业^_^

一.前言

  这三次大作业对我来说最大的拦路虎就是最后一道题,答题判题程序,难度从1到3节节攀升,尤其是第3次大作业,我感觉对我的打击是最大的,因为需要花很多时间去找到除了题目已给的输入输出样例。

  对于答题判题程序-1,我认为主要考察的知识点就是需要创建什么类,每个类中又有什么属性和方法,对于这道题只要把这几点做到了,难度并不大,更何况在题目的最下方老师贴心的给出了设计建议,然后我也按照老师给的这些建议去设计了类,但是我也是第一次接触到题量这么大的题目,所以也耗费了我很多时间去完成。

  对于答题判题程序-2,我认为主要考察的知识点是字符串的匹配判断,然后对字符串进行分段处理,这道题的难度相对于第一次的有些许上升,但并不大,可以基于第一次的代码上进行后续的修改。

  对于答题判题程序-3,我认为主要考察的知识点还是字符串的匹配,但是需要使用到正则表达式,这就要求对正则表达式掌握的比较充分,否则难度会非常大,而且这道题的题量和难度相对于前两次的来说跨度比较大,以至于我到最后都没有拿到满分,因为这道题目添加了很多隐藏的测试点,需要自己不断的去摸索,所以难度相对较大。

二.设计与分析、踩坑心得、改进建议

一:答题判题程序-1:

  这道题目我主要是按照题目给的设计建议来设计类的,这里我设计了三个类,分别是题目类Question、试卷类TestPaper、答卷类Answer。

1.题目类Question:
属性:
题目编号questionID(int类型)
题目内容content(String类型)
标准答案standardAnswer(String类型)

方法:
数据读写set\get方法
(对于这些方法,只需要简单的设置参数和返回类型即可)

判题方法
(该方法针对输入的答案进行判断)

  这里主要详细展示判题方法:

点击查看代码
//可以对输入的答案题目类中保存的标准答案进行对比,返回布偶值
public boolean isStandardAnswer(String answer) {
	return standardAnswer.trim().equals(answer.trim());
}
2.试卷类TestPaper:
属性:
题目数量questionNum(int类型)
题目列表questions(Question[]类型,是一个题目类的对象集合)。
 
方法:
判题(题号-num、答案-answer)
(该方法是对试卷中题号为num的题目进行批改)

保存题目(题号-num、题目-question)
(该方法是将题目类对象加入试卷类对象的题目列表中)

  这里详细展示两种方法:

点击查看代码
//判断答案-answer是否符合对应题号的题目标准答案-standardAnswer
	public boolean isAnswerRight(int questionID,String answer) {
		for(int i=0;i<this.questionNum;i++) {
			if(questions[i].questionID==questionID) {
				 return questions[i].isStandardAnswer(answer);
			}
		}
		return false;
	}
	
	//将题目保存到题目列表中,保存位置与num能对应
	public void saveQuestion(int questionID,Question questions) {
		this.questions[questionID-1] = questions;
	}
3.答卷类Answer:
属性:
试卷testPaper(TestPaper类型,试卷类)
答案列表answerCollection(String[]类型,是一个字符串类型的集合)
判题列表judgeList(boolean[]类型,是一个布偶类型的集合,保存试卷的每道题目的答题结果)

方法:
判题()
(该方法是批改试试卷中的所有题目)

保存一个答案(题号-num、答案-answer)
(该方法保存题号为num的题目的答题结果answer)

输出方法(题号-num)
(输出题号为num的题目的内容和答题结果)

  这里展示判题、保存一个答案这两种方法:

点击查看代码
//保存题号为num的题目的答题结果answer
    public void saveAnswer(int questionID,String answer) {
        this.answerCollection[questionID] = answer;
    }

    //判断答案列表中第num题的结果是否符合试卷中对应题号的题目标准答案
    public void questionJudge() {
        for(int i=0;i<testPaper.questionNum;i++) {
            judgeList[i] = testPaper.isAnswerRight(testPaper.questions[i].getquestionID(), answerCollection[i]);
        }
    }
4.主函数类
关键的方法就是String类的split()方法,它可以按特定的字符,将一整行字符串分为几个部分,这样的话我就可以将分段的这几个部分分别拿出来操作,非常方便,还有一个就是trim()方法,它可以除去字符串中多余的空格。

关键代码:

点击查看代码
for (int i=0;i<questionCount;i++) {
            String line = input.nextLine().trim();
            String[] parts = line.split("#");
            int questionID = Integer.parseInt(parts[1].split(":")[1].trim());
            String content = parts[2].split(":")[1].trim();
            String standardAnswer = parts[3].split(":")[1].trim();
            Question question = new Question(questionID, content, standardAnswer);
            testPaper.saveQuestion(questionID, question);
        }
        Answer answerSheet = new Answer(testPaper);
        String answersLine = input.nextLine().trim();
        String[] answerParts = answersLine.split("#");
        for (int j=1;j<answerParts.length;j++) {
            String answer = answerParts[j].split(":")[1].trim();
            answerSheet.saveAnswer(j - 1, answer);
        }

  这里我也设计了类图来表示它们之间的关系:
(由于当时发布这道题目的时候还没有学习类与类的关系,所以只画了类图但是不包含关系)
image

踩坑心得:
错误提交:
image

错误点1:

image

  这里没有注意题目号是从1开始的,但是数组是从下标0开始存储的。

错误点2:

image

  这里主要涉及到试卷类存答案的方法,这里使用了一个for循环,循环变量是i,而我直接将i作为参数放入该方法中,这样就导致一个问题,变量i是从0顺序增加到题目数量questionCount的,那么试卷类对象就会按顺序保存题目,这样的话就不满足乱序输入的条件了。
  所以我这里将i改为questionID,这样就可以乱序输入题目了。

测试样例:

image

  改前:
image
  改后:
image

改进建议:
  1.也许我可以把主函数中的按字符分割字符串的代码也封装为一个方法,然后放入到试卷类中,由试卷类的对象进行这个操作,这样的话我的主函数里就基本没有太多的代码,有的只是简单的创建类的对象代码,然后直接引用每个对象中的方法即可。
  2.这次作业中,每一个类中的属性我都没有对其密封处理,也就是说没有将其定义为私有类型,这样可能会带来几个后果,破坏封装原则、数据不一致、无法控制属性的访问级别等等,所以在接下来的大作业中,我也会严格的将属性定义为私有类型。

二:答题判题程序-2:

  这一次的题目在答题判题程序-1上增加了可输入多张试卷、试卷总分警示等格式要求。
  这一次我的设计是基于答题判题程序-1上的,这一次我设计的类也是总共分了三个,分别是题目类Question、试卷类TestPaper、答卷类Answer,题目类与答题判题程序-1的设计相同,但是后面两个类的设计有所变化。

1.题目类Question:
与答题判题程序-1相同。

2.试卷类TestPaper:
属性:
试卷ID testPaperID(int类型)
题目编号-分值列表questions(Map类型,保存每道题目的题号和对应的分值)
题目编号列表questionOrder(List类型,保存每道题目的题目ID)

方法:
添加题目(题号-questionID、分值-points)
(保存输入的题号与分值)

计算试卷总分值()
(该方法是将试卷中所有的题目分值累加计算)

判断试卷是否存在某个题目(题目ID)
(该方法是判断试卷中是否存在题目ID为输入值的题目)

  这里展示这三种方法,因为涉及到Map与List类的方法:

点击查看代码
public void addQuestion(int questionID,int points){
        questionPoints.put(questionID,points);
        questionOrder.add(questionID);
    }

    public int getTotalPoints(){
        int total = 0;
        for(int points : questionPoints.values()){
            total+=points;
        }
        return total;
    }

    public boolean containsQuestion(int questionID) {
        return questionPoints.containsKey(questionID);
    }
}
3.答卷类AnswerSheet:
属性:
试卷ID testPaperID(int类型)
答案列表answers(List类型,保存答题者写的答案)

方法:
添加写的答案(答案)
(将输入的答案保存到答案列表中)

  这里展示这种方法:

点击查看代码
public void addAnswer(String answer){
        answers.add(answer);
    }
4.主函数类
在答题判题程序-2的涉设计中,我把大多数的操作我放在了主函数中。

  操作一:根据不同类别的字符串进行分割,并将分割后的不同信息存入各个类的对象中。

点击查看代码
while(input.hasNextLine()){
            String line = input.nextLine().trim();
            if(line.equals("end")){
                break;
            }

            if(line.startsWith("#N:")){
                String[] parts = line.split("#");
                int questionID = Integer.parseInt(parts[1].split(":")[1].trim());
                String content = parts[2].split(":")[1].trim();
                String standardAnswer = parts[3].split(":")[1].trim();
                questionBank.put(questionID,new Question(questionID,content,standardAnswer));

            } else if (line.startsWith("#T:")) {
                String[] parts = line.split(" ");
                int testPaperID = Integer.parseInt(parts[0].split(":")[1]);
                TestPaper testPaper = new TestPaper(testPaperID);

                for (int i = 1;i< parts.length;i++){
                    String[] questionInfo = parts[i].split("-");
                    int questionID = Integer.parseInt(questionInfo[0].trim());
                    int points = Integer.parseInt(questionInfo[1].trim());
                    testPaper.addQuestion(questionID,points);
                }
                testPapers.put(testPaperID, testPaper);
            } else if (line.startsWith("#S:")) {
                String[] parts = line.split(" ");
                int testPaperID = Integer.parseInt(parts[0].split(":")[1].trim());
                AnswerSheet answerSheet = new AnswerSheet(testPaperID);
                for (int i = 1; i < parts.length; i++){
                    String answer = parts[i].split(":")[1].trim();
                    answerSheet.addAnswer(answer);
                }
                answerSheets.add(answerSheet);
            }
        }

  操作二:计算试卷总分值并判断是否有100分,没有则输出相关提示。

点击查看代码
for(TestPaper testPaper : testPapers.values()){
            if(testPaper.getTotalPoints() != 100){
                System.out.println("alert: full score of test paper" + testPaper.testPaperID + " is not 100 points");

            }
        }

  操作三:答卷,输出每个题目的答题情况,并计算所得总分,假如所答试卷不存在,输出相关提示。

点击查看代码
for(AnswerSheet answerSheet : answerSheets){
            TestPaper testPaper = testPapers.get(answerSheet.testPaperID);
            if(testPaper == null){
                System.out.println("The test paper number does not exist");
                continue;
            }
            int totalScore = 0;
            List<Integer> scores = new ArrayList<>();
            for (int i = 0;i<testPaper.questionOrder.size();i++){
                int questionID = testPaper.questionOrder.get(i);
                Question question = questionBank.get(questionID);
                String userAnswer = (i < answerSheet.answers.size()) ? answerSheet.answers.get(i) : "answer is null";
                boolean isCorrect = question.isStandardAnswer(userAnswer);
                if(userAnswer.equals("answer is null")){
                    System.out.printf("%s\n",userAnswer);
                }
                else {
                    System.out.printf("%s~%s~%s\n", question.content, userAnswer, isCorrect ? "true" : "false");
                }
                int score = isCorrect ? testPaper.questionPoints.get(questionID) : 0;
                scores.add(score);
                totalScore += score;
            }
            for (int i = 0; i < scores.size(); i++) {
                System.out.print(scores.get(i));
                if (i < scores.size() - 1) {
                    System.out.print(" ");
                }
            }
            System.out.println("~" + totalScore);
        }

  这里我也设计了类图,且把类与类之间的关系也建立了起来。
image

踩坑心得:
错误提交:
image

错误点1:

image

  缺少了答案缺失部分答案的情况,且格式输出有误。
测试样例:
image

  改前:
image

  改后:
image

改进建议:
  1.这次的大作业我也忽略了主函数代码太多的问题,我应该将主函数中的各种操作封装到各个类中,这样的话,在下一次的大作业中则可以直接使用各种类的接口,只需要稍微修改即可。
  2.这次作业中,还有一个问题就是,有些方法的圈复杂度较大,也就是if语句较多,可以改进为将if语句打包封装为一个小的方法,然后在其他方法中直接引用即可,这样的话就降低了代码阅读的复杂度。
  3.其实还有一个需要注意的地方就是,我的代码注释比较少,这一点我也将在后续的作业中改进,因为每一次的作业需要花费的时间都比较长,那么就会产生一个问题,忘了之前写的代码是干什么用的,所以这个时候就需要注释来帮助我们记忆它的作用,这样的话就能更好的帮助我们重新理解代码。

三:答题判题程序-3:

  这一次的题目在答题判题程序-2的基础上加入了学生信息、删除题目信息和格式错误信息提示等要求,除此之外,还有很多隐藏的格式要求,需要自己一点一点去试错,然后改进。
  这一次我重新设计了所有的类,有题目类、试卷类、答卷类、学生类、系统类。

1.题目类Question:
属性:
题目编号questionID(int类型)
题目内容questionContent(String类型)
标准答案standardAnswer(String类型)

方法:
数据读写set\get方法
(对于这些方法,只需要简单的设置参数和返回类型即可)
与之前的方法相似,这里不展示。

2.试卷类Question:
属性:
试卷编号 testPaperID(int类型)
题目ID列表questionIDs(List类型)
题目分值列表questionScores(List类型)
试卷总分totalScore(int类型)

方法:
除了一些数据get/set方法,主要有两个重要的方法:
添加题目信息(题目ID,分值)
(输入一个题目的ID号和其对应的分值,并将其保存)

  这里展示详细代码:

点击查看代码
public void addQuestion(int questionId, int score) {
        questionIDs.add(questionId);//给试卷添加题目ID
        questionScores.add(score);//给试卷添加题目分值
        totalScore += score;//计算总分值
    }
试卷总分不足100提示
(如果所有题目的总分值不足100,则输出相关提示)

  这里展示详细代码:

点击查看代码
//判断试卷总分值是否为100,返回布偶值
    public boolean isFullScore() {
        return totalScore == 100;
    }

    // 输出总分提示
    public void checkTotalScore() {
        if (!isFullScore()) {
            System.out.println("alert: full score of test paper" + this.testPaperID + " is not 100 points");
        }
    }
3.学生类Student:
属性:
学生ID studentID(String类型)
学生姓名studentName(String类型)

方法:
主要是get/set方法

  这里不展示详细代码。

4.答卷类AnswerSheet:
属性:
试卷ID testPaperID(int类型)
学生ID studentID(String类型)
答题列表Answers(Map类型)

方法:
关键方法-判题输出

(该方法有很多功能,主要是对每道题进行判分)
  这里展示该方法的关键部分:

点击查看代码
String answer = this.Answers.getOrDefault(i + 1, "null");//获得学生答卷第i题的答案
            if (answer.equals("null")) {//若为空
                System.out.println("answer is null");
                result.append("0");
                if(i!=testPaper.getQuestionIDs().size()-1)
                    result.append(" ");
                continue;
            }

            if (deletedQuestions.contains(qId)) {//如果该试卷上的题目i被删除,则输出相关信息,且直接开始判断下一题
                System.out.println("the question " + qId + " invalid~0");
                result.append("0");
                if(i!=testPaper.getQuestionIDs().size()-1)
                    result.append(" ");
                continue;
            }

            Question q = questions.get(qId);//获取ID为qId的题目
            if (q == null) {//如果改题目不存在
                System.out.println("non-existent question~0");
                result.append("0");
                if(i!=testPaper.getQuestionIDs().size()-1)
                    result.append(" ");
                continue;
            }

            boolean correct = answer.equals(q.getStandardAnswer());//判断答案对错
            System.out.println(q.getQuestionContent() + "~" + answer + "~" + (correct ? "true" : "false"));//输出
            result.append(correct ? score : 0);
            if(i!=testPaper.getQuestionIDs().size()-1)
                result.append(" ");
            if (correct) totalScore += score;
        }
        if (student == null) {//若没有输入学生信息,输出相关提示
            System.out.println(studentID+" not found");
            return;
        }
        else {
            result.append("~").append(totalScore);
            System.out.println(result.toString().trim());
        }
5.系统类SystemCenter:
属性:
题目列表 questions(Map类型)
试卷列表studentName(Map类型)
学生列表(Map类型)
答卷列表(List类型)
删除的题目列表(set类型)

方法:(这个类的重要方法都使用了正则表达式,这是最关键的部分)
处理题目信息

  这里展示详细代码。

点击查看代码
public  void processQuestion(String input) {
        Pattern pattern = Pattern.compile("#N:(\\d+) #Q:(.*) #A:(.*)");
        Matcher matcher = pattern.matcher(input);
        if (matcher.matches()) {
            int id = Integer.parseInt(matcher.group(1));
            String content = matcher.group(2);
            String answer = matcher.group(3);
            this.questions.put(id, new Question(id, content, answer));
        } else {
            System.out.println("wrong format:" + input);
        }
处理试卷信息
点击查看代码
public void processTestPaper(String input) {
        Pattern pattern1 = Pattern.compile("#T:(\\d+)");
        Matcher matcher1 = pattern1.matcher(input);
        if (matcher1.matches()){
            int paperId = Integer.parseInt(matcher1.group(1));
            TestPaper paper = new TestPaper(paperId);
            paper.addQuestion(-1, -1);
            this.testPapers.put(paperId, paper);
        }
        else {
            Pattern pattern2 = Pattern.compile("#T:(\\d+)((\\s+\\d+-\\d+)+)");
            //Pattern pattern = Pattern.compile("#T:(\\d+)(.*)");
            Matcher matcher2 = pattern2.matcher(input);
            if (matcher2.matches()) {
                int paperId = Integer.parseInt(matcher2.group(1));
                TestPaper paper = new TestPaper(paperId);
                String[] parts = matcher2.group(2).trim().split(" ");
                for (String part : parts) {
                    String[] qInfo = part.split("-");
                    int qId = Integer.parseInt(qInfo[0]);
                    int score = Integer.parseInt(qInfo[1]);
                    paper.addQuestion(qId, score);
                }
                this.testPapers.put(paperId, paper);
            } else {
                System.out.println("wrong format:" + input);
            }
        }
    }
处理学生信息
点击查看代码
public  void processStudent(String input) {
        Pattern pattern = Pattern.compile("#X:(.+)");
        Matcher matcher = pattern.matcher(input);
        if (matcher.matches()) {
            String[] parts = matcher.group(1).split("-");
            for (String studentInfo : parts) {
                String[] studentParts = studentInfo.split(" ");
                this.students.put(studentParts[0], new Student(studentParts[0], studentParts[1]));
            }
        } else {
            System.out.println("wrong format:" + input);
        }
    }
处理答卷信息
点击查看代码
public  void processAnswerSheet(String input) {
        Pattern pattern1 = Pattern.compile("#S:(\\d+) (\\d+)");
        Matcher matcher1 = pattern1.matcher(input);
        if (matcher1.matches()){
            int paperId = Integer.parseInt(matcher1.group(1));
            String studentId = matcher1.group(2);
            AnswerSheet sheet = new AnswerSheet(paperId, studentId);
            sheet.addAnswer(1, "-1");
            this.answerSheets.add(sheet);
        }
        else {
            Pattern pattern2 = Pattern.compile("#S:(\\d+) (\\d+)((\\s+#A:\\d+-\\S*)*)");
            Matcher matcher2 = pattern2.matcher(input);
            if (matcher2.matches()) {
                int paperId = Integer.parseInt(matcher2.group(1));
                String studentId = matcher2.group(2);
                AnswerSheet sheet = new AnswerSheet(paperId, studentId);
                String[] parts = matcher2.group(3).trim().split(" ");
                for (String part : parts) {
                    String[] ansInfo = part.substring(3).split("-");
                    int order = Integer.parseInt(ansInfo[0]);
                    String answer = ansInfo[1].trim();
                    sheet.addAnswer(order, answer);
                }
                this.answerSheets.add(sheet);
            } else {
                System.out.println("wrong format:" + input);
            }
        }
    }
处理删除题目信息
点击查看代码
public  void processDeleteQuestion(String input) {
        Pattern pattern = Pattern.compile("#D:N-(\\d+)");
        Matcher matcher = pattern.matcher(input);
        if (matcher.matches()) {
            int questionId = Integer.parseInt(matcher.group(1));
            this.deletedQuestions.add(questionId);
        } else {
            System.out.println("wrong format:" + input);
        }
    }
6.主函数类Main:
主函数中我创建了一个系统类的对象,然后直接引用它的各种方法对输入的信息保存然后分析处理,最后再直接引用它的方法输出。

  这里不展示详细代码。

  类图的设计:
这里可以看出其实设计的核心在于系统类,我设计的思路是,不管是题目、试卷、学生、答卷,其实他们都是在系统的安排下进行答题、判题、改分,最后出结果,所以可以看到系统类依赖于每一个类,且系统类与每一个类都存在聚合关系,即这些类随着系统的存在而存在,随着系统的消亡而消亡。
image

踩坑心得
  其实我到现在为止都还没有拿到满分,但是中间我也发现了一个已经纠正过来的踩坑行为。
就是当我输入的试卷信息为空的时候:

输入样例:
#N:1 #Q:1+1= #A:2
#T:1
#X:20201103 Tom
#S:1 20201103 #A:1-2
end
输出结果:
alert: full score of test paper1 is not 100 points
20201103 Tom: 

这个测试点:
image
我现在已经改过来了,之前这个测试点没通过是因为,当我输入上面那个测试样例后,会输出非零返回。

改进建议
  其实我自己认为,这次在设计方面已经比前两次做的好多了,首先我把 所有的属性都设为了私有属性,而且我基本上把所有的功能都封装到了各个类中作为一个方法来引用,这样我的主函数中基本没有太多的多余的操作代码。
  但是我发现这次可能因为设计的类太多了,导致我去试错那些隐藏的测试点的时候,很难去修改代码,因为只要修改了一个地方,那么另一个地方也要跟着修改很大一部分,所以我应该更加精简的设计类,不至于当我需要修改的时候太麻烦而无法进行。

总结
  经过这三次大作业的洗礼,我从中学到了非常多的知识,比如String类的split方法,它可以按某一个字符分割字符串,还有Map类型的数据,它里面有两个类型,一个是key键值,还有一个是对应键值的value值,使用起来非常方便,比如第三次大作业,我就不需要设计两个属性来保存,试卷中的题目ID和它对应的分值,我可以直接设计一个Map类型的属性,将其作为一个题目列表,保存每一个题目ID对应的分值。除此之外,我还学到了如何使用正则表达式,假如第三次大作业没有使用正则表达式,那我估计代码得有将近七八百行了。
  但是我还有很多不足的地方,因为我第三次作业到现在都还没有拿到满分,其实这对我的打击挺大的,当然我也不会气馁,因为还有第四次大作业等着我,我必须得把这些问题先解决了,才能顺利的完成后续的作业。
  关于课堂的学习,我还是比较希望老师上课能选取大作业中一些比较难的测试点来稍微讲解一下的,因为对我来说有些隐藏的测试点我真的很难找到,呜呜呜。

posted on 2024-10-26 13:23  jkjkjkkjkjkj  阅读(63)  评论(0)    收藏  举报