前三次PTA大作业总结

wcn269 / 2024-04-21 / 原文

前言:

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

  • 知识点:每个题目集的最后一个大题都要求了考察面向对象程序与设计中的单一职责原则,迪米特法则和对类的设计,类设计的模板格式、正则表达式的熟悉和使用等知识点。而最后一题前面的几个小题则是拿来给大家练练手的,提升大家对于java语法和类的设计的熟练度。

  • 题量和难度:我觉得题目虽然说一星期只有一道题,但就难度来说确实有点大,而且题目没看个二十来分钟是完全不能够准确地进行类的设计和代码结构设计的,特别是对我来说没有用过正则表达式并且对其并不熟悉比如说:不知道该如何将#N,#Q,#A后面的内容分割开来。这使得我感到难度增大。对于那些小题目也有些超标的就比如说第二次大作业里面的“手机按价格排序、查找”这一题就考察到了我们暂时还未接触到的接口类的设计及其实现同时此题也考查到了之前c语言学习过的链表的使用转化为了java中的链表的应用,也算是从c语言到java的过渡题。

设计与分析:

答题判题程序-1

  • 题目内容
    设计实现答题程序,模拟一个小型的测试,要求输入题目信息和答题信息,根据输入题目信息中的标准答案判断答题的结果。
    输入格式:
    程序输入信息分三部分:

1、题目数量

格式:整数数值,若超过1位最高位不能为0,

样例:34

2、题目内容

一行为一道题,可以输入多行数据。

格式:"#N:"+题号+" "+"#Q:"+题目内容+" "#A:"+标准答案

样例 1+1=~2
2+2= ~4
3、判题信息

判题信息为一行数据,一条答题记录每个答案的判断结果,答案的先后顺序与题目题号相对应。

格式:判题结果+" "+判题结果

格式约束:

1、判题结果输出只能是true或者false,
2、判题信息的顺序与输入答题信息中的顺序相同
样例:true false true

由于我先前第一次写这个题目并不了解题目的套路和技巧导致得了低分,但好在这周老师重新发布了一回这三个大题的总结题集并且经过我在ieda里面反复钻研和询问那些满分同学的技巧我也是成功写了出来。
以下是源码: import java.util.*;

public class Main {

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();//这个是题目数量
    Paper[] paper=new Paper[n];
    Question[] Question=new Question[n];
    AnswerPaper[] answerpaper=new AnswerPaper[n];
    for(int i=0;i<n;i++){
        paper[i]=new Paper();
        Question[i]=new Question();
        answerpaper[i]=new AnswerPaper();
    }
    sc.nextLine();//接收数字后面的空格
    for (int i = 0; i < n; i++) {
        String line = sc.nextLine();
        int num1= Integer.parseInt(line.substring(line.indexOf("#N:") + 3, line.indexOf("#Q:")).trim())-1;
        String  content1= line.substring(line.indexOf("#Q:") + 3, line.indexOf("#A:")).trim();
        String  standardAnswer1= line.substring(line.indexOf("#A:") + 3).trim();
        Question[num1].setterContent(content1);
        paper[num1].add(Question[num1]);
        paper[num1].setStandarAnswer(standardAnswer1);
    }
    for (int i=0;i<n;i++){
        String answerInput = sc.next();
        String answers = answerInput.substring(3);
        answerpaper[i].setAnswers(answers);
    }
    for(int i=0;i<n;i++){
        System.out.println(paper[i].getQuestion().getterContent()+"~"+answerpaper[i].getAnswer());
    }
    for(int i=0;i<n;i++){
        if(i==n-1){
            System.out.print(paper[i].judgment(answerpaper[i]));
        }
        else {
            System.out.print(paper[i].judgment(answerpaper[i])+" ");
        }
    }
}
  }


 class Question{

int num;
String content;
String standardAnswer;
public Question(){

}
public Question(int num,String content,String standardAnswer){
    this.num=num;
    this.content=content;
    this.standardAnswer=standardAnswer;
}
public int getterNum(){
    return num;
}
public void setterNum(int num){
    this.num=num;
}
public String getterContent(){
    return content;
}
public void setterContent(String content){
    this.content=content;
}
public String getterStandardAnswer(){
    return standardAnswer;
}
public void setterStandardAnswer(String standardAnswer){
    this.standardAnswer=standardAnswer;
}
public boolean judgment(String answer){
    return answer.equals(standardAnswer);
}
}
  class Paper{
List<Question> Questionlist;
Question Question;
String standaranswer;
int num;
public Paper(){

}
public void add(Question Question){
    this.Question=Question;
}
public void setStandarAnswer(String standaranswer){
    this.standaranswer=standaranswer;
}

public Boolean judgment(AnswerPaper answer){
    return standaranswer.equals(answer.getAnswer());
}
public int getterNum() {
    return num;
}
public Question getQuestion(){
    return Question;
}

public List<Question> getterQuestionlist() {
    return Questionlist;
}
}
  class AnswerPaper{
Paper paper;
List<String> answers;
List<Boolean> judgment;
String answer;
public AnswerPaper(){

}
public AnswerPaper(Paper paper) {
    this.paper = paper;
    this.answers = new ArrayList<>();
    this.judgment = new ArrayList<>();
    for (int i = 0; i < paper.getterQuestionlist().size(); i++) {
        judgment.add(null);
        answers.add("");
    }
}
public void setAnswers(String answer){
    this.answer=answer;
}
public String getAnswer(){
    return answer;
}
public void judge(int num) {
    String correctAnswer = paper.getterQuestionlist().get(num - 1).getterStandardAnswer();
    if (answers.get(num - 1).equals(correctAnswer)) {
        judgment.set(num - 1, true);
    } else {
        judgment.set(num - 1, false);
    }
}
public String output(int num) {
    Question Question = paper.getterQuestionlist().get(num - 1);
    String result = judgment.get(num - 1) ? "true" : "false";
    String outputStr = Question.content + "~" + answers.get(num - 1)+ "\n" ;
    return outputStr;
}
public void saveAnswer(int num, String answer) {
    answers.set(num - 1, answer);
}
}

  • 分析:
  • 1.Main类
    这是程序的主入口,负责控制整体流程。

初始化:通过Scanner对象从控制台读取输入数据。首先读取一个整数n,表示题目数量。接着创建了三个数组,分别存储Paper(试卷)、Question(题目)和AnswerPaper(答题纸)对象。

读取题目信息:使用for循环遍历n次,每次读取一行字符串并解析出题目编号num1、题目内容content1和标准答案standardAnswer1。然后调用相应的方法设置Question对象的内容,并将其添加到对应的Paper对象中。同时,将标准答案保存在Paper对象中。

读取考生答案:再次使用for循环读取考生对每个题目的答案。将答案保存在AnswerPaper对象中。

输出题目及答案:遍历所有题目,打印出每道题的内容及其对应的考生答案。

判断答案是否正确:最后,遍历所有题目,调用Paper对象的judgment方法判断考生答案是否与标准答案相符。将结果(布尔值)逐个打印出来,最后一个结果后面不加空格。

  • 2.Question类

表示一道题目,包含以下属性和方法:

num:题目编号(整数)
content:题目内容(字符串)
standardAnswer:标准答案(字符串)
提供了对应的getter和setter方法,以及一个judgment方法用于判断传入的答案是否与标准答案相同。

  • 3.Paper类

表示一份试卷,包含以下属性和方法:

Questionlist:一个List,存储该试卷中的所有题目
Question:当前题目对象(单个题目)
standaranswer:标准答案(字符串)
提供了add方法用于向试卷中添加题目,setStandarAnswer方法设置标准答案,以及一个judgment方法用于判断传入的答题纸对象中对应题目的答案是否与标准答案相同。

  • 4.AnswerPaper类

表示一张答题纸,包含以下属性和方法:
paper:对应试卷对象
answers:一个List,存储考生对各题目的答案
judgment:一个List,记录各题目答案的判断结果(正确或错误)
提供了setAnswers方法设置考生答案,getAnswer方法获取考生答案,judge方法用于判断指定题目答案是否正确并更新judgment列表,output方法生成输出字符串(包括题目内容、考生答案及判断结果),以及saveAnswer方法保存考生对指定题目的答案。

  • 答题判题程序-2
  • 相较于第一题,本次的第二题改动为:
    1.程序输入信息分三种,三种信息可能会打乱顺序混合输入;
    2.允许题目编号有缺失,例如:所有输入的题号为1、2、5,缺少其中的3号题。此种情况视为正常;
    3.试卷信息的格式:"#T:"+试卷号+" "+题目编号+"-"+题目分值;
    4.答卷信息的改动:格式为"#S:"+试卷号+" "+"#A:"+答案内容;
    5.试卷总分警示
    该部分仅当一张试卷的总分分值不等于100分时作提示之用,试卷依然属于正常试卷,可用于后面的答题。如果总分等于100分,该部分忽略,不输出。

import java.util.*;

// 题目类
class Question {
private int number;
private String content;
private String answer;

public Question(int number, String content, String answer) {
    this.number = number;
    this.content = content;
    this.answer = answer;
}

public int getNumber() {
    return number;
}

public String getContent() {
    return content;
}

public String getAnswer() {
    return answer;
}
}

// 试卷类
class TestPaper {
private int paperNumber;
private Map<Integer, Integer> questionScores;

public TestPaper(int paperNumber) {
    this.paperNumber = paperNumber;
    this.questionScores = new HashMap<>();
}

public int getPaperNumber() {
    return paperNumber;
}

public void setQuestionScore(int questionNumber, int score) {
    questionScores.put(questionNumber, score);
}

public int getQuestionScore(int questionNumber) {
    return questionScores.getOrDefault(questionNumber, 0);
}

// 添加公共方法以便外部类访问题目分数
public Map<Integer, Integer> getQuestionScores() {
    return questionScores;
}
}

// 答卷类
class AnswerSheet {
private int paperNumber;
private List<String> answers;

public AnswerSheet(int paperNumber) {
    this.paperNumber = paperNumber;
    this.answers = new ArrayList<>();
}

public int getPaperNumber() {
    return paperNumber;
}

public void addAnswer(String answer) {
    answers.add(answer);
}

public List<String> getAnswers() {
    return answers;
}
}

public class Main {
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    List<Question> questions = new ArrayList<>();
    Map<Integer, TestPaper> testPapers = new HashMap<>();

    // 读取输入信息
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        if (line.equals("end")) {
            break;
        }

        if (line.startsWith("#N:")) {
            String[] parts = line.split(" ");
            int number = Integer.parseInt(parts[0].substring(3));
            String content = parts[1].substring(3);
            String answer = parts[2].substring(3);
            questions.add(new Question(number, content, answer));
        } else if (line.startsWith("#T:")) {
            String[] parts = line.split(" ");
            int paperNumber = Integer.parseInt(parts[0].substring(3));
            TestPaper testPaper = new TestPaper(paperNumber);
            for (int i = 1; i < parts.length; i++) {
                String[] pair = parts[i].split("-");
                int questionNumber = Integer.parseInt(pair[0]);
                int score = Integer.parseInt(pair[1]);
                testPaper.setQuestionScore(questionNumber, score);
            }
            testPapers.put(paperNumber, testPaper);
        }
    }

    // 判断试卷总分是否为100
    for (TestPaper testPaper : testPapers.values()) {
        int totalScore = testPaper.getQuestionScores().values().stream().mapToInt(Integer::intValue).sum();
        if (totalScore != 100) {
            System.out.println("alert: full score of test paper" + testPaper.getPaperNumber() + " is not 100 points");
        }
    }

    List<AnswerSheet> answerSheets = new ArrayList<>();
    AnswerSheet currentAnswerSheet = null;

    // 继续读取输入信息
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        if (line.equals("end")) {
            break;
        }

        if (line.startsWith("#S:")) {
            if (currentAnswerSheet != null) {
                answerSheets.add(currentAnswerSheet);
            }
            String[] parts = line.split(" ");
            int paperNumber = Integer.parseInt(parts[0].substring(3));
            currentAnswerSheet = new AnswerSheet(paperNumber);
        } else if (line.startsWith("#A:")) {
            if (currentAnswerSheet != null) {
                currentAnswerSheet.addAnswer(line.substring(4));
            }
        }
    }
    if (currentAnswerSheet != null) {
        answerSheets.add(currentAnswerSheet);
    }

    // 对答题信息进行判分
    for (AnswerSheet answerSheet : answerSheets) {
        TestPaper testPaper = testPapers.get(answerSheet.getPaperNumber());
        if (testPaper == null) {
            System.out.println("The test paper number does not exist");
            continue;
        }
        
        List<String> answers = answerSheet.getAnswers();
        int totalScore = 0;
        StringBuilder gradingInfo = new StringBuilder();
        for (int i = 0; i < answers.size(); i++) {
            String answer = answers.get(i);
            if (i >= questions.size()) {
                System.out.println("answer is null");
                gradingInfo.append("0 ");
                continue;
            }
            
            Question question = questions.get(i);
            int questionNumber = question.getNumber();
            String correctAnswer = question.getAnswer();
            boolean isCorrect = answer.equals(correctAnswer);
            int score = isCorrect ? testPaper.getQuestionScore(questionNumber) : 0;
            totalScore += score;
            gradingInfo.append(score).append(" ");
            System.out.println(question.getContent() + "~" + answer + "~" + isCorrect);
        }
        System.out.println(gradingInfo.toString().trim() + "~" + totalScore);
    }
}
}

  • 总结分析
    代码问题:类的设计过程中并未遵循开闭原则,即一个类应该对扩展开放,对修改关闭。
    不能够基于第一次作业的代码来进行扩展类的数量,而这也进一步说明了我对于类的设计不够熟练,虽说在补录中成功攻略第一题但我到现在为止任然通过不了测试点,连基本的测试的输出都不能打印出来一个样例,这也进一步说明了我对于正则表达式的陌生,不能将其基本掌握的问题。
    同时我也发现我应该从总体出发,构建一个基本的框架,进而一开始的时候只需要进行扩展类的数量就够了,而非在源代码上进行修改。

  • 答题判题程序-3
    题目变得更长了,相对于第二题,本题的改动为:
    1.增加了学生信息、删除题目信息的要求;
    2.程序输入信息变为5种;
    3.试卷信息格式为:"#T:"+试卷号+" "+题目编号+"-"+题目分值+" "+题目编号+"-"+题目分值+...;
    4.删除题目信息

    删除题目信息为独行输入,每一行为一条删除信息,多条删除信息可分多行输入。该信息用于删除一道题目信息,题目被删除之后,引用该题目的试卷依然有效,但被删除的题目将以0分计,同时在输出答案时,题目内容与答案改为一条失效提示,例如:”the question 2 invalid~0”

    格式:"#D:N-"+题目号;
    5.添加了学号,试卷号引用错误提示信息;

总结分析:
这题依旧是0分,至于为什么没有源码是因为我第二题那个日期题都没写出来!!!

而经过第二次未达标转而来到第三次时,难度无疑是断崖式的提升,较第二题而言此题变得更加神秘莫测,据说众多选手都在此栽了跟头,而且我也是没看懂题目的意思,只是大概懂了一些内容,并不能够完全将其理解透彻,还是跟第二次作业一样的原因并不能够对源代码进行扩展。而且由于第二题的失利导致我对其产生了畏难心理,故而花了很少的时间在这上面。

踩坑心得:

  • 1.对于第一个大作业而言,在我们读取完第一行的数字之后需要添加一行sc.nextLine();//接收数字后面的空格代码以接收数字后面的空格。

    否则就会呈现这样一幅景象:

很明显这是由于缓冲区的空格依旧存在导致后面输入的信息无法被传入所引发的问题。

  • 2.在第二次题目集中第一个小问
    要求该类实现Comparable接口,重写compareTo方法,在此我们需要对class MobilePhone后面添加上implements Comparable
    唯有这样上述才能实现Comparable接口的MobilePhone类。

    当我们缺失时便会产生如下结果:

    测试部分会告诉我们方法Collections.<T#1>sort(List<T#1>)不适用: 当直接调用Collections.sort(phones)时,编译器尝试使用无参数版本的sort方法,
    其签名如下:
    public static <T extends Comparable<? super T>> void sort(List list)
    这个方法要求传入的列表元素类型T必须实现Comparable接口。然而,由于MobilePhone类虽然实现了Comparable接口,但在编译器看来,泛型参数T#1与MobilePhone之间的关系存在冲突。
    具体来说,编译器认为T#1具有不兼容的边界:

    等价约束要求T#1必须是MobilePhone类型;
    下界约束要求T#1必须是Comparable<? super T#1>的子类型。
    

    这两个约束无法同时满足,导致编译器无法确定T#1的具体类型,从而无法应用该方法。

方法Collections.<T#2>sort(List<T#2>, Comparator<? super T#2>)不适用: 编译器还尝试使用接受一个Comparator参数的sort方法版本,其签名如下:

  public static <T> void sort(List<T> list, Comparator<? super T> comparator)

但由于仅提供了List作为参数,而没有提供所需的Comparator实例,所以编译器无法匹配该方法签名。此外,由于实际和形式参数列表长度不同,编译器无法推断出泛型参数T#2的类型。

综上所述,编译错误的原因在于您直接调用了Collections.sort()方法对一个ArrayList进行排序,但编译器无法找到一个适用于MobilePhone类型的无参数版本的sort方法。要解决这个问题,您需要确保MobilePhone类正确实现了Comparable接口,并确保比较逻辑符合预期。如果您需要自定义排序规则,可以提供一个适当的Comparator实例作为第二个参数传递给Collections.sort()方法。

  • 3.在所有问题中我们应该重点关注类的设计而非一上来就直接开始写代码,可以适当地进行类图的创建。就比如我三大题的第一大题便是没有好好地规划类的形式和框架,导致第二次作业直接变成了重构代码......

  • 4.在我们第一次作业集的第四问“成绩计算-2-关联类”中输入格式提到了这么一句:

题目提及“3个学生的课程顺序可能会不一致”这里困扰了我老久了 ,因为我一直以为每门课都需要进行平均分的计算,直到我再次读了好几遍题目,并且重点关注了一下输入输出的结果才发现:我们这个输出的结果是不按科目顺序来的,而且它只要算期中、期末的总分再除以科目的数量就直接可以求出平均值来,而不需要考虑分科的情况。

改进建议:

  • 1.我们的题目集最后一题都是采取的迭代类型,一题套一题,一题接一题,故而我们应该严格遵循着我们的java七大设计原则,无论是单一职责原则,还是开闭原则,亦或是迪米特法则.......我们都需要在我们类的设计中围绕着它们进行设计并以此进行结构框架的构建。

  • 2.根据我们题目要求的行为,我们需要进行认真鉴别:这个行为是否能够单独构成一个类呢?
    而且我们还需要考虑到我们众多类之间存在着什么关系:是依赖?关联?聚焦?还是泛化?
    许多的行为我们都可以单独拎出来,而非是放在单独某个固定的类中。

  • 3.对于我们的正则表达式,我觉得我还是并没有将其基本掌握,故而我觉得应该进一步学习正则表达式的使用,必要时自己单独练习。

  • 4.在java中仍存在许多值得我们学习的地方,就比如ArrayList、HashMap、LinkedList、等知识,还有众多java类,如:LocalDate、LocalTime、ZonedDateTime等可方便我们对直接时间进行管理和操作。

总结:

  • 1.通过此次三大作业的实践操作和练习,相信我们大部分同学都已经可以从c语言过渡到java中,并将课本上前八章的语法内容转化为实践操作的方法了,进而进一步加深我们对java语法和使用格式的印象。

  • 2.在我们的学习方面应掌握许多更为高级的方法,例如调用LocalTime等类直接对数据的操作进行进一步简化,提高我们的效率,再如正则表达式对数据进行直接切割......

  • 3.对于老师发的大题设计思路我觉得还是应该描述的更为详细点,有某些地方对于同学们并不友好,而且类图中并无注释,让人难以理解此方法或此类的用意。

  • 4.对于课程而言,我们确实需要学习极其多的知识,掌握极其多的方法和技巧,而更重要的是打好相应的基础,只有基础足够牢固,我们才能进一步地提升自己的编程能力。故而我建议大家可以去B站、mooc等众多学习软件上进行相应知识的扩充和补充。