NCHU-OOP课程与PTA一到三周作业总结

NCHU-OOP课程与PTA一到三周作业总结

引言


关于面向对象:

  从上学期C的面向过程无脑写过渡到现在的面向对象程序设计,说来简单,把类看作带函数的结构体就行,但实际在此基础上还有诸多面向对象的技术要学习,也有很多新的概念和思考方式。这几周算是逐步开始面向对象的学习,后面要学东西还挺多挺难的,唉,慢慢学吧。

关于PTA题目:

​  面向对象的题目已经做了三周,知识点包括:

    1. java基础语法,输入输出,字符串,数组类的熟悉

    2. 类和对象的创建和使用

    3. 类间关联聚集关系的实现编写

    4. 基础的程序设计原则

    5. 正则表达式处理字符串

    6. LocalDate,Collections,Arrays等内部类的使用等

​  第一次题目共五道题,属于熟悉语法阶段较为简单,题量适中

​    7-1设计一个风扇Fan类

​    7-2类和对象的使用

​​    7-3成绩计算-1-类、数组的基本运用

​​    7-4成绩计算-2-关联类

​​    7-5答题判题程序-1

​​  第二次题目共四道,最后一道在上一次的基础上更为着重考验对程序中类间关系的设计,题量适中

​    7-1手机按价格排序、查找

​    7-2sdut-oop-4-求圆的面积(类与对象)

​    7-3Java类与对象-汽车类

​    7-4答题判题程序-2

​​  第三次共三题,最后一道依旧在上一次的基础上进行迭代,但逻辑较复杂代码量较大且描述上有点模糊,有点坑,还是挺需要花时间去设计钻研的

​    7-1面向对象编程(封装性)

​    7-2jmu-java-日期类的基本使用

​    7-3答题判题程序-3

​  三次题目印象最深刻的无疑就是第三次,前两天狠狠加班了好久也没做出来,改了几次逻辑关系,直接红温了,但没想到是正则表达式出了问题;

课程内容总结:


程序设计准则:

​​  老师说教的就是设计,所以简单总结一下。

​    1. SOC和SRP:分离关注点和单一职责原则;

​    2. 高内聚,低耦合的设计理念;

​    3. LSP里氏代换原则:子类可以扩展父类的功能,但不能改变父类原有的功能,即出现父类的地方都可以用子类替换。但子类最好不要新增功能方法,让子类出现的地方也能用父类替换(两者方法尽量一致)从而便于实现多态;

​    4. OCP开闭原则:对扩展开放, 对修改封闭

​    5. DIP依赖倒转原则:抽象不应该依赖细节,细节应该依赖于抽象。面向抽象变成而不是面向具象编程;具体类间不要有关系,而是抽象类之间有关系;

​    6. CRP合成复用原则:对于实现代码复用,优先考虑聚集而不是继承

类间关系:

​​    类间关系有 关联依赖聚合(包括组合和聚合),继承(泛化);

正则表达式:

​    相关链接:

​​    正则表达式语法速查|正则教程 (stackoverflow.org.cn)

​​    正则表达式在线测试 | 菜鸟工具 (jyshare.com)

​    
​    
​  以上三点既是这三周的学习内容收获,也是这三周的题目集主要考察的内容。

PTA-oop-1


答题判断程序-1:

题目:

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

输入格式:
程序输入信息分三部分:


1、题目数量

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

   样例:34

2、题目内容

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

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

格式约束:题目的输入顺序与题号不相关,不一定按题号顺序从小到大输入。

   样例:#N:1 #Q:1+1= #A:2

         #N:2 #Q:2+2= #A:4

3、答题信息

   答题信息按行输入,每一行为一组答案,每组答案包含第2部分所有题目的解题答案,答案的顺序号与题目题号相对应。

   格式:"#A:"+答案内容

   格式约束:答案数量与第2部分题目的数量相同,答案之间以英文空格分隔。

   样例:#A:2 #A:78

      2是题号为1的题目的答案
      78是题号为2的题目的答案
   答题信息以一行"end"标记结束,"end"之后的信息忽略。

输出格式:
1、题目数量

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

   样例:34

2、答题信息

   一行为一道题的答题信息,根据题目的数量输出多行数据。

   格式:题目内容+" ~"+答案

   样例:1+1=~2

          2+2= ~4

3、判题信息

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

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

   格式约束:

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

输入样例1:
单个题目。例如:

1
#N:1 #Q:1+1= #A:2
#A:2
end
输出样例1:
在这里给出相应的输出。例如:

1+1=~2
true
输入样例2:
单个题目。例如:

1
#N:1 #Q:1+1= #A:2
#A:4
end

题目分析:

​​  审题后可知,本题目是对输入的题目和答案进行判断输出;简单分析可知首先要对输入的字符进行处理,获取题目或者回答,然后对每组回答,找到对应题目并进行判断输出;因而至少需要题目类(Problem)存放题目,试卷(Paper)类存放题目集,答卷类(AnswerPaper)存放所有回答;根据单一职责原则等,还需要Judge类来判断题目正确与否以及输出,Input类来对输出进行处理获取有用的信息;Input只负责对数据进行处理,处理后的数据放入Judge中的试卷和答卷中,随后通过Judge进行判断;

​  在我初次设计中,没有创建input类,而是创建了两个类SplitInputQuestion和SplitInputProblem处理输入数据放入Judge中的试卷和答卷,再调用Judge的方法判断输出;

类图设计:

​​  本题类图设计如下:

需要插入的图片

方法编写:

  1. 主函数中调用Split的两个类的方法获取题目和答卷并放入,然后调用Judge等中的方法,进行判断;
public class Main {
	public static void main(String[] args) {
		
		Judge judge = new Judge();
		
		Scanner in = new Scanner(System.in);
		
		String input = in.nextLine();
		int num = Integer.parseInt(input);
		judge.getAnswerPaper().setAnswersNum(num);
		judge.getPaper().setProblemsNum(num);
		
		for(int i = 0;i < num;i ++) {
			input = in.nextLine();
			SplitInputQuestion inputQuestion = new SplitInputQuestion(input);
			judge.getPaper().inputProblem(inputQuestion.split());
		}
		input = in.nextLine();
		SplitInputAnswer splitInputAnswer = new SplitInputAnswer(input);
		judge.getAnswerPaper().setAnswers(splitInputAnswer.split());
		
		input = in.nextLine();
		
		judge.judgeAnswers();
		
	}
}

  1. 通过SplitInputProblem中的split方法通过正则表达式等提取出题目信息返回字符串数组
public Problem split() {
		  
		  int num = 0;
		  String question = "",answer = "";
		  
	      inputTmp = input.split("#");
	      for(int i = 0;i < inputTmp.length;i ++) {
	    	  if(inputTmp[i].indexOf("N:") >= 0) {//inputTmp[i].matches("N:")
	    		  inputTmp[i] = inputTmp[i].replaceAll("N:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  num = Integer.parseInt(inputTmp[i]);
	    	  }
	    	  else if(inputTmp[i].indexOf("Q:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("Q:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  question = inputTmp[i];
	    	  }
	    	  else if(inputTmp[i].indexOf("A:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("A:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  answer = inputTmp[i];
	    	  }
	      }
  1. SplitInputQuestion中的split方法通过正则表达式等提取出题回答信息返回字符串
public String[] split() {
	      inputTmp = input.split("#");
	      for(int i = 0;i < inputTmp.length;i ++) {
    		inputTmp[i] = inputTmp[i].replaceAll("A:","");
  		  	inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
    		inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	      }
	      
	      inputTmp = Arrays.copyOfRange(inputTmp, 1, inputTmp.length + 1);
	      return inputTmp;
	   }

  1. Judge类中两个判题时的方法如下:

    outputProblemAndAnswer()先输出题目和回答

    再用outputAnswer()判断题目正误并输出

public void outputProblemAndAnswer() {
		   for(int i = 0;i < paper.getProblemsNum();i ++) {
			   System.out.println(paper.getProblems()[i].getQuestion() + "~" + answerPaper.getAnswers()[i]);
		   }
	   }
	   
public void outputAnswer() {

    for(int i = 0;i < paper.getProblemsNum();i ++) {
        if(paper.getProblems()[i].judgeAnswer(answerPaper.getAnswers()[i]))
            System.out.print("true");
        else
            System.out.print("false");

        if(i != paper.getProblemsNum() - 1)
            System.out.print(" ");

    }

}
  1. 利用工具生成其它类的getter和setter等方法,编写判断和输出的方法等;

  2. 通过以上代码实现对输入题目的判断输出;

  3. 完整源代码见 NCHU-OOP课程与PTA一到三周作业源代码 - cdz_hy - 博客园 (cnblogs.com)

代码分析:

​  对本次代码用Source Monitor进行分析:

需要插入的图片

​  代码共283行,143个语句,分为7个类,平均方法4.71个方法,方法平均语句数2.64;类的数量还是可以的,没有一main到底;

​  从左侧图可以看出代码平均复杂度较低,平均语句和方法数较少,注释较少;因而在今后的编码过程中需要增加注释的数量,增加可读性,毕竟代码是给人读的;

​  圈复杂度均小于4且集中在1到2(不过这种较简单程序要是大于4就说不过去了);

​​  注:圈复杂度(值等于流程图中边数 - 点数 + 2)是一种代码复杂度的衡量标准,可简单理解为覆盖所有分支或情况时所需测试用例组数

踩坑总结:

  1. 本题的类设计在我现在看来还是有值得优化的地方的,比如SplitInputQuestion和SplitInputProblem 这两个类可以合并成同一个Input()类,这个类只对输入的字符串进行处理,获取有用信息返回;这样程序更有条理,而且更具备扩展性,在输入复杂的情况下可以直接增加判断和更改;

  2. 同时在主方法中,不应该和SplitInputQuestion,SplitInputProblem这两个类有关系,也不应该在这里把问题和回答放入试卷和答卷,应当再创建一个控制类减少耦合,把它们放入;主方法调用控制类,控制类中来调用并处理输入的字符串,同时将题目和回答等放入控制类中Judge对象中的答卷试卷,最后调用Judge的方法判断输出;

    优化后参考类图如下:

    需要插入的图片

  1. 本题中对于题目,答卷等数据的存储都是用的数组形式来存的,但是这种数据结构是有缺陷的,在删除和长度扩充等操作上比较麻烦,可以在今后采取ArrayList等容器来存储;

  2. 这个程序算是这开始一段时间中写的第一个比较长的程序,也是第一次考验我们对于类,对于面向对象的掌握,也许这个题目并不是很难,但我觉得它算是我们在面向对象这条路上的引路人,让我们开始了oop学习之旅;

PTA-oop-2


答题判断程序-2:

题目:

设计实现答题程序,模拟一个小型的测试,以下粗体字显示的是在答题判题程序-1基础上增补或者修改的内容。

要求输入题目信息、试卷信息和答题信息,根据输入题目信息中的标准答案判断答题的结果。

输入格式:

程序输入信息分三种,三种信息可能会打乱顺序混合输入:

1、题目信息

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

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

格式约束:

    1、题目的输入顺序与题号不相关,不一定按题号顺序从小到大输入。
    2、允许题目编号有缺失,例如:所有输入的题号为1、2、5,缺少其中的3号题。此种情况视为正常。
   样例:#N:1 #Q:1+1= #A:2

         #N:2 #Q:2+2= #A:4

2、试卷信息

一行为一张试卷,可输入多行数据(多张卷)。

格式:"#T:"+试卷号+" "+题目编号+"-"+题目分值

     题目编号应与题目信息中的编号对应。

     一行信息中可有多项题目编号与分值。
样例:#T:1 3-5 4-8 5-2

3、答卷信息

    答卷信息按行输入,每一行为一张答卷的答案,每组答案包含某个试卷信息中的题目的解题答案,答案的顺序与试卷信息中的题目顺序相对应。

   格式:"#S:"+试卷号+" "+"#A:"+答案内容

   格式约束:答案数量可以不等于试卷信息中题目的数量,没有答案的题目计0分,多余的答案直接忽略,答案之间以英文空格分隔。

   样例:#S:1 #A:5 #A:22

       1是试卷号 

       5是1号试卷的顺序第1题的题目答案

       22是1号试卷的顺序第2题的题目答案
   答题信息以一行"end"标记结束,"end"之后的信息忽略。

输出格式:

1、试卷总分警示

该部分仅当一张试卷的总分分值不等于100分时作提示之用,试卷依然属于正常试卷,可用于后面的答题。如果总分等于100分,该部分忽略,不输出。

   格式:"alert: full score of test paper"+试卷号+" is not 100 points"

   样例:alert: full score of test paper2 is not 100 points

2、答卷信息

   一行为一道题的答题信息,根据试卷的题目的数量输出多行数据。

   格式:题目内容+"~"+答案++"~"+判题结果(true/false)

约束:如果输入的答案信息少于试卷的题目数量,答案的题目要输"answer is null"   

样例:3+2=~5~true

         4+6=~22~false.

      answer is null
3、判分信息

   判分信息为一行数据,是一条答题记录所对应试卷的每道小题的计分以及总分,计分输出的先后顺序与题目题号相对应。

   格式:题目得分+" "+....+题目得分+"~"+总分

   格式约束:

 1、没有输入答案的题目计0分

 2、判题信息的顺序与输入答题信息中的顺序相同
   样例:5 8 0~13

根据输入的答卷的数量以上2、3项答卷信息与判分信息将重复输出。

4、提示错误的试卷号

如果答案信息中试卷的编号找不到,则输出”the test paper number does not exist”,参见样例9。


设计建议:

参考答题判题程序-1,建议增加答题类,类的内容以及类之间的关联自行设计。

输入样例1:
一张试卷一张答卷。试卷满分不等于100。例如:

#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 2-8
#S:1 #A:5 #A:22
end
输出样例1:
在这里给出相应的输出。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
2+2=~22~false
0 0~0
输入样例2:
一张试卷一张答卷。试卷满分不等于100。例如:

#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-70 2-30
#S:1 #A:5 #A:22
end
输出样例2:
在这里给出相应的输出。例如:

1+1=~5~false
2+2=~22~false
0 0~0
输入样例3:
一张试卷、一张答卷。各类信息混合输入。例如:

#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-70 2-30
#N:3 #Q:3+2= #A:5
#S:1 #A:5 #A:4
end
输出样例:
在这里给出相应的输出。例如:

1+1=~5~false
2+2=~4~true
0 30~30
输入样例4:
试卷题目的顺序与题号不一致。例如:

#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
end
输出样例:
在这里给出相应的输出。例如:

2+2=~5~false
1+1=~22~false
0 0~0
输入样例5:
乱序输入。例如:

#N:3 #Q:3+2= #A:5
#N:2 #Q:2+2= #A:4
#T:1 3-70 2-30
#S:1 #A:5 #A:22
#N:1 #Q:1+1= #A:2
end
输出样例:
在这里给出相应的输出。例如:

3+2=~5~true
2+2=~22~false
70 0~70
输入样例6:
乱序输入+两份答卷。例如:

#N:3 #Q:3+2= #A:5
#N:2 #Q:2+2= #A:4
#T:1 3-70 2-30
#S:1 #A:5 #A:22
#N:1 #Q:1+1= #A:2
#S:1 #A:5 #A:4
end
输出样例:
在这里给出相应的输出。例如:

3+2=~5~true
2+2=~22~false
70 0~70
3+2=~5~true
2+2=~4~true
70 30~100
输入样例7:
乱序输入+分值不足100+两份答卷。例如:

#N:3 #Q:3+2= #A:5
#N:2 #Q:2+2= #A:4
#T:1 3-7 2-6
#S:1 #A:5 #A:22
#N:1 #Q:1+1= #A:2
#S:1 #A:5 #A:4
end
输出样例:
在这里给出相应的输出。例如:

alert: full score of test paper1 is not 100 points
3+2=~5~true
2+2=~22~false
7 0~7
3+2=~5~true
2+2=~4~true
7 6~13
输入样例8:
乱序输入+分值不足100+两份答卷+答卷缺失部分答案。例如:

#N:3 #Q:3+2= #A:5
#N:2 #Q:2+2= #A:4
#T:1 3-7 2-6
#S:1 #A:5 #A:22
#N:1 #Q:1+1= #A:2
#T:2 2-5 1-3 3-2
#S:2 #A:5 #A:4
end
输出样例:
在这里给出相应的输出。例如:

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
输入样例9:
乱序输入+分值不足100+两份答卷+无效的试卷号。例如:

#N:3 #Q:3+2= #A:5
#N:2 #Q:2+2= #A:4
#T:1 3-7 2-6
#S:3 #A:5 #A:4
end
输出样例:
在这里给出相应的输出。例如:

alert: full score of test paper1 is not 100 points
The test paper number does not exist

题目分析:

​  本题在上一道判题程序-1的基础上新增了试卷的组建和按试卷答题;因此对于类的设计,应当在1的基础上新增Paper试卷类(不过好在上次就已经有了),同时因为试卷会有多张,故新增Papers试卷集合类;每个回答会有特定的一些属性,所以要有Answer答案类,本次的答卷也会有多张多次回答,所以应该增加Answerpapers答卷集合类;另外,吸取上次的经验教训,用Input类处理数据,用Controller类解耦合,把Judge类的判断功能集合在Controller中,main只和Controller打交道。

​  main调用controller中Input判断输入类型的方法,返回一个数,分别进行不同操作;如判断输入的是答卷信息时,main调用controller中的创建放入答卷方法(此方法再调用Input中处理答卷信息的方法获得有效信息),放入controllr的answerpapers中;

​  需要注意的是,在输入题目的时候,controller先将题目都加入其中的problemsTmp(Paper类的对象),存储所有题目信息,作为题库,当需要创建试卷时再去其中寻找并加入试卷;

​  在输入结束后,main通过controller对试卷成绩进行分析,不满100提示;随后对所有输入的答卷进行判断并输出;

类图设计:

​  本次类图设计如下:

需要插入的图片

方法编写:

  1. 实现Input中对输入的内容的判断judgeInput(),通过判断输入是什么从而实现不同操作
    public int judgeInput() {
        //通过判断输入中有什么来判断输入的内容,采取不同操作
        if(input.indexOf("#N:") >= 0)
            return 1;
        else if(input.indexOf("#T:")  >= 0)
            return 2;
        else if(input.indexOf("#S:") >= 0)
            return 3;
        else if(input.indexOf("end") >= 0)
            return 4;
        else
            return 0;
    }
  1. 对于不同的输入内容,通过正则表达式采取不同分割来获取有效信息;

    若为试卷信息,返回一个数组,第一位是试卷编号,第二位开始,一个题目编号,一个题目分值;

    若为题目,返回一个题目对象;

    若为回答,返回数组,依次为各个回答;

	   public int[] getPaper() {
		   //将输入的试卷信息放入数组中,第一位是试卷编号,第二位开始,一个题目编号,一个题目分值
		   
		   //去掉T等字符
		   input = input.replaceAll("#T:", "");
		   //去掉开头结尾空格,防止数组分割多了
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   //-替换为空格
		   input = input.replaceAll("-", " ");
		   
		   String[] inputTmp = input.split("\\s+");
		   int[] inputTmpNum = new int[inputTmp.length];
		   for(int i = 0;i < inputTmp.length;i ++) {
			   inputTmpNum[i] = Integer.parseInt(inputTmp[i]);
		   }
	      return inputTmpNum;
	   }
	   
	   public Problem getProblem() {
		   
		   String[] inputTmp;
		   int num = 0;
		   String question = "",answer = "";
		   
		 //去掉开头#,防止数组分割多了
		   input = input.replaceAll("^#", "");
		   inputTmp = input.split("#");
		   
	      for(int i = 0;i < inputTmp.length;i ++) {
	    	  if(inputTmp[i].indexOf("N:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("N:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  //去掉所有空格后转换
	    		  num = Integer.parseInt(inputTmp[i]);
	    	  }
	    	  else if(inputTmp[i].indexOf("Q:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("Q:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  question = inputTmp[i];
	    	  }
	    	  else if(inputTmp[i].indexOf("A:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("A:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  answer = inputTmp[i];
	    	  }
	      }
	      
	      Problem problemTmp = new Problem(num, question, answer);
	      
	      return problemTmp;
	   }
	   
	   public String[] getAnswer() {

		   //去掉S,A等字符
		   input = input.replaceAll("#S:", "");
		   input = input.replaceAll("#A:", "");
		   //去掉开头结尾空格,防止数组分割多了
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   
		   String[] inputTmp = input.split("\\s+");
		   
	      return inputTmp;
	   }
	  
	   
  1. Controller来调用Input中的方法以获得有效信息,并把有效信息创建为对象放入自己的papers,answerpapers等属性中,所以编写对应方法;
	   //输入题目
	   public void inputProblemsTmp() {
		   Problem problemTmp = input.getProblem();
		   //每次添加后problemNum增加(在input时增加,集成在Paper中)
		   problemsTmp.inputProblem(problemTmp);
	   }
	   
	   
	   //创建试卷
	   public void inputPaper() {
		   Paper paperTmp = new Paper();
		   
		   //根据input返回的数组编排试卷并放入试卷集
		   int[] numTmp = input.getPaper();
		   //第一位是试卷编号
		   paperTmp.setNum(numTmp[0]);
		   
		   for(int i = 1;i < numTmp.length;i += 2) {
			   int hasFound = -1;
			   
			   //先找到这个题
			   for(int k = 0;k < problemsTmp.getProblemsNum();k ++ ) {
				   if(problemsTmp.getProblems().get(k).getNum() == numTmp[i]) {
					   hasFound = k;//存这个题号在第几个
					   break;
				   }
			   }
			   
			   //没找到报错,找到放入并给分数,更新试卷总分
			   if(hasFound >= 0) {
				   //此处get的应该是numTmp中的数减1,因为list从0开始而题目编号从1开始!
				   paperTmp.inputProblem(problemsTmp.getProblems().get(hasFound));
				   
				   //为什么在第二次创建试卷时改变score会改变第一个试卷中题目的score?
				   //原因是因为add进入的实质是把这个对象的管理者(指针)放在了容器中,而在不同试卷中调用同一道题的过程
				   //实际上都是对那同一个管理者进行的操作,改的都是那个题的score!!!
				   
				   
				   //唯一解决办法就是在把problem add 进paper时(改inputProblem方法)再创一个problemTmp ,然后把要加入的题的值一一复制给Tmp,把Tmp加入paper中
				   //这样才能保证在不同试卷中调用同一个题的时候不会改同一个内存区域的problem
				   
				   paperTmp.getProblems().get(paperTmp.getProblemsNum() - 1).setScore(numTmp[i + 1]);
				   paperTmp.setScoreAll(paperTmp.getScoreAll() + numTmp[i + 1]);
				   
			   }
			   else {
				   System.out.println("The problem number does not exist");
			   }
		   }
		   
		  
		   papers.inputPaper(paperTmp);
		   
	   }
	   
	   //创建答卷
	   public void inputAnswerPaper() {
		   AnswerPaper answerPaperTmp = new AnswerPaper();
		   
		   String[] answersTmp = input.getAnswer();
		   
		 //第一位是试卷编号
		   answerPaperTmp.setNum(Integer.parseInt(answersTmp[0]));
		   
		   for(int i = 1;i < answersTmp.length;i ++) {
			   //题号从1开始,放入答案,更新个数(在input时就更新了)
			   Answer answerTmp = new Answer(Integer.parseInt(answersTmp[0]), i, answersTmp[i]);
			    answerPaperTmp.inputAnswer(answerTmp);
		   }
		   
		   answerPapers.inputAnswer(answerPaperTmp);
	   }
	 
	   
4. 编写Controller中判断试卷分值是否满100的方法
	   public void checkPaper() {
		   //其实感觉在这之前也要对试卷们按照试卷编号排序
		   //papers.sortPapers();
		   
		   for(int i = 0;i < papers.getPaperNum();i ++) {
			   if(papers.getPapers().get(i).getScoreAll() != 100) {
				   System.out.println("alert: full score of test paper" + papers.getPapers().get(i).getNum() + " is not 100 points");
			   }
		   }
		   
	   }
	   
  1. 编写Controller中判题并输出的方法

    以试卷中的题目为主进行遍历!

    先遍历输出问题,回答和正误;

    再遍历计算输出分数;

	   //对输入的答卷依次判断并输出
	   public void judge() {
		   
		   for(int i = 0;i < answerPapers.getAnswerspaperNum();i ++) {
			   
			   //先判断是否有这张试卷
			   int hasFound = -1;
			   for(int k = 0;k < papers.getPaperNum();k ++) {
				   if(papers.getPapers().get(k).getNum() == answerPapers.getAnswerPapers().get(i).getNum()) {
					   hasFound = k;
					   break;
				   }
			   }
			   
	
			   if(hasFound >= 0) {
				   
				   //找到这张试卷后,从第一个开始输出答案和回答等
				   for(int k = 0;k < papers.getPapers().get(hasFound).getProblemsNum();k ++) {
					   System.out.println(outputAnswer(hasFound, k, i, k));
				   }
				   
				   //再次遍历得获得分数
				   int scoreAll = 0;
				   for(int k = 0;k < papers.getPapers().get(hasFound).getProblemsNum();k ++) {
					   int scoreTmp =  outputScore(hasFound, k, i, k);
					   scoreAll += scoreTmp;
					   System.out.print(scoreTmp);
					   if(k != papers.getPapers().get(hasFound).getProblemsNum() - 1) 
						   System.out.print(" ");
				   }
				   System.out.println("~" + scoreAll);
				   
			   }
			   else {
				   System.out.println("The test paper number does not exist");
			   }
		   }
		   
	   }
	   
	   //输出题目和答案等(返回字符串)
	   public String outputAnswer(int paperNum,int problemNum,int answerPaperNum,int answerNum) {
		   if(answerNum >= answerPapers.getAnswerPapers().get(answerPaperNum).getAnswerNum()) {
			   return "answer is null";
		   }
		   else {
			   if(papers.getPapers().get(paperNum).getProblems().get(problemNum).judgeAnswer(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(answerNum).getAnswer())) {
				   return "" + papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion() + "~" + 
						   answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(answerNum).getAnswer() + "~true";
			   }
			   else {
				   return "" + papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion() + "~" + 
						   answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(answerNum).getAnswer() + "~false";
			   }
			   
		   }
		   
	   }
	   
	   
	 //判断分数,返回分数
	   public int outputScore(int paperNum,int problemNum,int answerPaperNum,int answerNum) {
		   if(answerNum >= answerPapers.getAnswerPapers().get(answerPaperNum).getAnswerNum()) {
			   return 0;
		   }
		   else {
			   if(papers.getPapers().get(paperNum).getProblems().get(problemNum).judgeAnswer(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(answerNum).getAnswer())) {
				   return papers.getPapers().get(paperNum).getProblems().get(problemNum).getScore();
			   }
			   else {
				   return 0;
			   }
			   
		   }
		   
	   }
	   
  1. 按分析中的逻辑关系编写Main类中的主方法
	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);
		
		int isEnd = 0;
		String input = "";
		Controller controller = new Controller();
		
		//循环输入,遇end结束
		while(true) {
			
			input = in.nextLine();
			controller.getInput().setInput(input);
			
			switch(controller.getInput().judgeInput()) {
				case 1:
					controller.inputProblemsTmp();
					break;
				case 2:
					controller.inputPaper();
					break;
				case 3:
					controller.inputAnswerPaper(); 
					break;
				default:
					isEnd = 1;
					break;
			}
			
			//判断是否需要退出输入
			if(isEnd == 1)
				break;
			
		}
		
		//检查试卷分数是否满100
		controller.checkPaper();
		
		//检查对错并输出(输出题目,答案和总分)(集成在judge)
		controller.judge();
		
		
		
	}
  1. 生成其它类中的各种getter,setter等方法;

    还有把题目放入试卷,试卷放入题目集中的input方法;

    特别注意,在把题目放入试卷时需要深拷贝(此处简单地用给每个属性复制一边实现),这样才能给一道题在不同试卷中赋上不同的分数值!

    原因见Controller中的inputPaper注释;

  2. 完整源代码见 NCHU-OOP课程与PTA一到三周作业源代码 - cdz_hy - 博客园 (cnblogs.com)

代码分析:

​  对本次代码用Source Monitor进行分析:

需要插入的图片

​  可以得知代码共654行,297个语句,分为8个类,平均9个方法,方法平均语句数3.76;

​  从左侧图可以看出代码平均复杂度依旧略低,毕竟没有加什么算法,基本都是查找判断;

​  平均语句和方法数略少,注释略少;

​  圈复杂度小于5,集中在1到2;这应该是因为需要判断的输入类型多了,主函数中的switch语句更复杂导致的;

踩坑总结:

  1. 让我印象最深刻的无疑就是在把题库中找到的problem赋值成绩并且放入试卷paper中时,如果一道题目同时在多个试卷中被引用而且成绩不同时就会导致这道题的成绩出现很大问题,在输出时分数都会是最后一次调用这道题时赋的分数;

    原因找了半天,才发现因为我在放入题目到试卷中的时候是通过arrarylist中的add方法直接加入,但是这个add进入的实质是把这个对象的管理者(指针)放在了容器中,而在不同试卷中调用同一道题的过程实际上都是对那同一个管理者进行的操作,改的都是那个题的score;而这个对于管理者(指针/引用的)赋值也是java与c的一个显著不同;

    因此应该使用强复制,但是我这里就简单地通过new一个新对象,把每个值复制给它再放入arraylist解决了;

  2. 部分eclipse版本调试时有问题,没法看arraylist容器中的对象的具体属性的值;俗话说的好磨刀不误砍柴工,但是我为了找到并解决上面说的这个问题,硬生生在eclipse上搞了很久很久,还是没能让它显示出详细信息,也没找到问题所在,非常浪费时间;后来改用Idea一会就发现了,因此强烈推荐用Idea进行调试等 QAQ;

  3. 本题是一个系列,是不断迭代的,正如老师第一次所提醒的要构造设计好,本次的编写过程中我就深受上次没搞好的亏,几乎算是又重构了一次,这按理说是不应该出现的;总之,要设计好,一定要设计好,搞懂类与类间应该是什么关系;

​  

PTA-oop-3


java-日期类的基本使用:

题目:

给定一个日期,判定是否为合法日期。如果合法,判断该年是否闰年,该日期是当年第几天、当月第几天、当周第几天、。
给定起始日期与结束日期,判定日期是否合法且结束日期是否早于起始日期。如果均合法,输出结束日期与起始日期之间的相差的天数、月数、念书。
输入格式:
第一行输入一个日期字符串,格式为"YYYY-MM-dd"
第二行输入两个日期字符串,中间使用空格隔开。分别代表开始日期与结束日期。

输出格式:
如果第一行日期字符串非法,输出自定义的错误信息。
如果第一行日期有效,输出相关信息,如果是闰年要输出是闰年。
如果第二行两个日期,只要有一个无效。就输出相关错误信息。
如果第二行两个日期有效且结束日期不早于开始日期,输出相关信息。

输入样例1:
第一行日期非法、第二行有日期非法

2020-02-30
2020-02-30 2020-01-02
输出样例1:
2020-02-30无效!
2020-02-30或2020-01-02中有不合法的日期.
输入样例2:
均有效且合法

2021-02-28
2019-08-01 2020-01-02
输出样例2:
2021-02-28是当年第59天,当月第28天,当周第7天.
2020-01-02与2019-08-01之间相差154天,所在月份相差-7,所在年份相差1.
输入样例3:
日期均有效,但结束日期早于开始日期

2020-02-28
2020-02-02 2020-02-01
输出样例3:
2020-02-28是闰年.
2020-02-28是当年第59天,当月第28天,当周第5天.
2020-02-01早于2020-02-02,不合法!

题目分析:

​  本题需要使用到Java内部的日期类LocalDate

​  通过LocalDate.of(year,month,day)来对LocalDate对象赋值年月日;

​  通过localDateBefore.until(localDateAfter, ChronoUnit.DAYS)来计算两个LocalDate对象间相差的天数;

​  对于LocalDate类中数据的输出:要先创建格式对象DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");再通过创建的字符串String dateString = localDate1.format(dateTimeFormatter)接受格式化后的输出数据,再输出这个字符串;

​  

​  参考链接:

​  [Java 8 新特性]Java LocalDate 详解-CSDN博客

​  java8 LocalDate的使用、LocalDate格式化_localdate.parse-CSDN博客

​  

答题判断程序-3:

题目:

设计实现答题程序,模拟一个小型的测试,以下粗体字显示的是在答题判题程序-2基础上增补或者修改的内容,要求输入题目信息、试卷信息、答题信息、学生信息、删除题目信息,根据输入题目信息中的标准答案判断答题的结果。

输入格式:

程序输入信息分五种,信息可能会打乱顺序混合输入。

1、题目信息
题目信息为独行输入,一行为一道题,多道题可分多行输入。

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

格式约束:
    1、题目的输入顺序与题号不相关,不一定按题号顺序从小到大输入。
    2、允许题目编号有缺失,例如:所有输入的题号为1、2、5,缺少其中的3号题。此种情况视为正常。
样例:#N:1 #Q:1+1= #A:2
     #N:2 #Q:2+2= #A:4
     
2、试卷信息

  试卷信息为独行输入,一行为一张试卷,多张卷可分多行输入数据。
格式:"#T:"+试卷号+" "+题目编号+"-"+题目分值+" "+题目编号+"-"+题目分值+...

格式约束:
   题目编号应与题目信息中的编号对应。
   一行信息中可有多项题目编号与分值。 
样例:#T:1 3-5 4-8 5-2   
     
3、学生信息

  学生信息只输入一行,一行中包括所有学生的信息,每个学生的信息包括学号和姓名,格式如下。

格式:"#X:"+学号+" "+姓名+"-"+学号+" "+姓名....+"-"+学号+" "+姓名     

格式约束:
    答案数量可以不等于试卷信息中题目的数量,没有答案的题目计0分,多余的答案直接忽略,答案之间以英文空格分隔。
 样例:
       #S:1 #A:5 #A:22
       1是试卷号 
       5是1号试卷的顺序第1题的题目答案 
4、答卷信息

  答卷信息按行输入,每一行为一张答卷的答案,每组答案包含某个试卷信息中的题目的解题答案,答案的顺序号与试 卷信息中的题目顺序相对应。答卷中:

格式:"#S:"+试卷号+" "+学号+" "+"#A:"+试卷题目的顺序号+"-"+答案内容+...

    

格式约束:
       答案数量可以不等于试卷信息中题目的数量,没有答案的题目计0分,多余的答案直接忽略,答案之间以英文空格分隔。
       答案内容可以为空,即””。
       答案内容中如果首尾有多余的空格,应去除后再进行判断。
样例:
       #T:1 1-5 3-2 2-5 6-9 4-10 7-3
       #S:1 20201103 #A:2-5 #A:6-4
       1是试卷号
       20201103是学号
       2-5中的2是试卷中顺序号,5是试卷第2题的答案,即T中3-2的答案 
       6-4中的6是试卷中顺序号,4是试卷第6题的答案,即T中7-3的答案 
注意:不要混淆顺序号与题号
 

5、删除题目信息

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

格式:"#D:N-"+题目号

格式约束:

       题目号与第一项”题目信息”中的题号相对应,不是试卷中的题目顺序号。

       本题暂不考虑删除的题号不存在的情况。      
样例:
#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 2-8
#X:20201103 Tom-20201104 Jack
#S:1 20201103 #A:1-5 #A:2-4
#D:N-2
end

输出
alert: full score of test paper1 is not 100 points
1+1=~5~false
the question 2 invalid~0
20201103 Tom: 0 0~0
     答题信息以一行"end"标记结束,"end"之后的信息忽略。

输出格式:


1、试卷总分警示


该部分仅当一张试卷的总分分值不等于100分时作提示之用,试卷依然属于正常试卷,可用于后面的答题。如果总分等于100 分,该部分忽略,不输出。


格式:"alert: full score of test paper"+试卷号+" is not 100 points"

 样例:alert: full score of test paper2 is not 100 points


2、答卷信息


一行为一道题的答题信息,根据试卷的题目的数量输出多行数据。

格式:题目内容+"~"+答案++"~"+判题结果(true/false)

约束:如果输入的答案信息少于试卷的题目数量,每一个缺失答案的题目都要输出"answer is null" 。
样例:
     3+2=~5~true
     4+6=~22~false.
     answer is null
     

3、判分信息

 判分信息为一行数据,是一条答题记录所对应试卷的每道小题的计分以及总分,计分输出的先后顺序与题目题号相对应。

    格式:**学号+" "+姓名+": "**+题目得分+" "+....+题目得分+"~"+总分

    格式约束:

     1、没有输入答案的题目、被删除的题目、答案错误的题目计0分
     2、判题信息的顺序与输入答题信息中的顺序相同
    样例:20201103 Tom: 0 0~0

       根据输入的答卷的数量以上2、3项答卷信息与判分信息将重复输出。
 
4、被删除的题目提示信息


当某题目被试卷引用,同时被删除时,答案中输出提示信息。样例见第5种输入信息“删除题目信息”。


5、题目引用错误提示信息


试卷错误地引用了一道不存在题号的试题,在输出学生答案时,提示”non-existent question~”加答案。例如:

输入:

#N:1 #Q:1+1= #A:2
#T:1 3-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:1-4
end

输出:
alert: full score of test paper1 is not 100 points
non-existent question~0
20201103 Tom: 0~0
如果答案输出时,一道题目同时出现答案不存在、引用错误题号、题目被删除,只提示一种信息,答案不存在的优先级最高,例如:
输入:
#N:1 #Q:1+1= #A:2
#T:1 3-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103
end

输出:
alert: full score of test paper1 is not 100 points
answer is null
20201103 Tom: 0~0

6、格式错误提示信息


输入信息只要不符合格式要求,均输出”wrong format:”+信息内容。

      例如:wrong format:2 #Q:2+2= #4
7、试卷号引用错误提示输出


如果答卷信息中试卷的编号找不到,则输出”the test paper number does not exist”,答卷中的答案不用输出,参见样例8。


8、学号引用错误提示信息


如果答卷中的学号信息不在学生列表中,答案照常输出,判分时提示错误。参见样例9。


本题暂不考虑出现多张答卷的信息的情况。


输入样例1:
简单输入,不含删除题目信息。例如:

#N:1 #Q:1+1= #A:2
#T:1 1-5
#X:20201103 Tom
#S:1 20201103 #A:1-5
end
输出样例1:
在这里给出相应的输出。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
20201103 Tom: 0~0
输入样例2:
简单输入,答卷中含多余题目信息(忽略不计)。例如:

#N:1 #Q:1+1= #A:2
#T:1 1-5
#X:20201103 Tom
#S:1 20201103 #A:1-2 #A:2-3
end
输出样例3
简单测试,含删除题目信息。例如:

alert: full score of test paper1 is not 100 points
1+1=~2~true
20201103 Tom: 5~5
输入样例3:
简单测试,含删除题目信息。例如:

#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 2-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:1-5 #A:2-4
#D:N-2
end
输出样例3:
在这里给出相应的输出,第二题由于被删除,输出题目失效提示。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
the question 2 invalid~0
20201103 Tom: 0 0~0
输入样例4:
简单测试,含试卷无效题目的引用信息以及删除题目信息(由于题目本身无效,忽略)。例如:

#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 3-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:1-5 #A:2-4
#D:N-2
end
输出样例4:
输出不存在的题目提示信息。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
non-existent question~0
20201103 Tom: 0 0~0
输入样例5:
综合测试,含错误格式输入、有效删除以及无效题目引用信息。例如:

#N:1 +1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 2-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:1-5 #A:2-4
#D:N-2
end
输出样例5:
在这里给出相应的输出。例如:

wrong format:#N:1 +1= #A:2
alert: full score of test paper1 is not 100 points
non-existent question~0
the question 2 invalid~0
20201103 Tom: 0 0~0
输入样例6:
综合测试,含错误格式输入、有效删除、无效题目引用信息以及答案没有输入的情况。例如:

#N:1 +1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 2-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:1-5
#D:N-2
end
输出样例6:
答案没有输入的优先级最高。例如:

wrong format:#N:1 +1= #A:2
alert: full score of test paper1 is not 100 points
non-existent question~0
answer is null
20201103 Tom: 0 0~0
输入样例7:
综合测试,正常输入,含删除信息。例如:

#N:2 #Q:2+2= #A:4
#N:1 #Q:1+1= #A:2
#T:1 1-5 2-8
#X:20201103 Tom-20201104 Jack-20201105 Www
#S:1 20201103 #A:2-4 #A:1-5
#D:N-2
end
输出样例7:
例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
the question 2 invalid~0
20201103 Tom: 0 0~0
输入样例8:
综合测试,无效的试卷引用。例如:

#N:1 #Q:1+1= #A:2
#T:1 1-5
#X:20201103 Tom
#S:2 20201103 #A:1-5 #A:2-4
end
输出样例8:
例如:

alert: full score of test paper1 is not 100 points
The test paper number does not exist
输入样例9:
无效的学号引用。例如:

#N:1 #Q:1+1= #A:2
#T:1 1-5
#X:20201106 Tom
#S:1 20201103 #A:1-5 #A:2-4
end
输出样例9:
答案照常输出,判分时提示错误。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
20201103 not found

输入样例10:
信息可打乱顺序输入:序号不是按大小排列,各类信息交错输入。但本题不考虑引用的题目在被引用的信息之后出现的情况(如试卷引用的所有题目应该在试卷信息之前输入),所有引用的数据应该在被引用的信息之前给出。例如:

#N:3 #Q:中国第一颗原子弹的爆炸时间 #A:1964.10.16
#N:1 #Q:1+1= #A:2
#X:20201103 Tom-20201104 Jack-20201105 Www
#T:1 1-5 3-8
#N:2 #Q:2+2= #A:4
#S:1 20201103 #A:1-5 #A:2-4
end
输出样例10:
答案按试卷中的题目顺序输出。例如:

alert: full score of test paper1 is not 100 points
1+1=~5~false
中国第一颗原子弹的爆炸时间~4~false
20201103 Tom: 0 0~0

题目分析:

​  本题依旧是在判题程序-2的基础上迭代而来的,本次新增了学生信息,还有删除题目的操作。同时对错误格式的输入需要返回WrongFormat;

​  新增学生信息就要新增Student学生类,同时为了方便之后的扩展,使程序更有条理性,增加Class班级类存放学生;

​  主要的处理逻辑同上一题不变,但为了处理新的输入需要在input中和controller中新增对应的方法或属性;

​  为了使得格式错误的输入被准确的筛选出来,需要重写input类中对输入的判断方法,对每一种用严格的正则表达式来约束,如果不符合即输出报错;

类图设计:

​  本次类图设计如下:

需要插入的图片

方法编写:

  1. 新增Student和Class两个类,并通过工具生成getter,setter方法;

  2. 修改input中判断输入的是什么信息的judgeInput方法:

    通过与严格表示正常格式的正则表达式进行matches,如果不符合所有则非法;

	   public int judgeInput() {
		   
		 //需要对输入完善判断(要保证了输入必须严格按照格式,序号只能为数字等)
		 //通过判断输入中有什么来判断输入的内容,采取不同操作
		      if(input.matches("#N:\\d+\\s+#Q:[^#]*#A:.*"))	
		    	  return 1;
		      else if(input.matches("(#T:\\d+){1}(\\s+[0-9]+-[0-9]+){0,}\\s*"))
		    	  return 2;
			  else if(input.matches("(#S:\\d+\\s+\\d+\\s*){1}(\\s*#A:\\s*\\d+-[^#]*){0,}\\s*"))
		    	  return 3;
		      else if(input.matches("(\\s*#X:\\s*){1}(\\d+\\s+[^- ]+-){0,}(\\d+\\s+.+){1}\\s*"))
		    	  return 4;
		      else if(input.matches("\\s*#D:\\s*N-\\d+\\s*"))
		    	  return 5;
		      else if(input.indexOf("end") >= 0)
		    	  return 6;
		      //若非以上情况,说明输入更不合法!返回0
		      else
		    	  return 0;
		      
	   }
  1. 因为输入时候的格式较上一题有差别,所以修改Input中那几个获取有效信息的方法;

​  特别注意,回答可以为空,题目答案也可以为空;回答可以为一句话,题目答案也可以是一句话;所以要考虑好正则表达式和split切割的方式;在Controller中放入答卷的时候要对于最后一道答案为空的时候进行特殊判断,因为无论怎么切割,最后一道答案为空时,这个回答都没法被切割出来;

	   //获得创建的试卷中的信息(放入哪些题还有分数)
	   public int[] getPaper() {
		   //将输入的试卷信息放入数组中,第一位是试卷编号,第二位开始,一个题目编号,一个题目分值
		   
		   //去掉T等字符
		   input = input.replaceAll("#T:", "");
		   //去掉开头结尾空格,防止数组分割多了
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   //-替换为空格
		   input = input.replaceAll("-", " ");
		   
		   String[] inputTmp = input.split("\\s+");
		   int[] inputTmpNum = new int[inputTmp.length];
		   for(int i = 0;i < inputTmp.length;i ++) {
			   inputTmpNum[i] = Integer.parseInt(inputTmp[i]);
		   }
	      return inputTmpNum;
	   }
	   
	   //获得题目
	   public Problem getProblem() {
		   
		   String[] inputTmp;
		   int num = 0;
		   String question = "",answer = "";
		   
		 //去掉开头#,防止数组分割多了
		   input = input.replaceAll("^#", "");
		   inputTmp = input.split("#");
		   
	      for(int i = 0;i < inputTmp.length;i ++) {
	    	  if(inputTmp[i].indexOf("N:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("N:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  //去掉所有空格后转换
	    		  num = Integer.parseInt(inputTmp[i]);
	    	  }
	    	  else if(inputTmp[i].indexOf("Q:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("Q:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  question = inputTmp[i];
	    	  }
	    	  else if(inputTmp[i].indexOf("A:") >= 0) {
	    		  inputTmp[i] = inputTmp[i].replaceAll("A:","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("^\\s+","");
	    		  inputTmp[i] = inputTmp[i].replaceAll("\\s+$","");
	    		  answer = inputTmp[i];
	    	  }
	      }
	      
	      Problem problemTmp = new Problem(num, question, answer);
	      
	      return problemTmp;
	   }
	   
	   
	   //获取单次(单张试卷)的回答
	   //oop3中试卷答案的输入格式改了,每个回答都有对应的题目
	   //同时因为答案可以为空,所以需要修改分割的逻辑!
	   //最重要的是回答可以为一句话!!!
	   public String[] getAnswer() {
		   
		   //去掉S字符
		   input = input.replaceAll("#S:", "");
		   
		   //去掉开头结尾空格,防止数组分割多了
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   
		   //以-或#A:为界限分割
		   String[] inputTmp = input.split("-|#A:");
		   //再遍历一次分割后的字符串数组,去掉不该有的空格
		   for(int i = 0;i < inputTmp.length;i ++) {
			   inputTmp[i] = inputTmp[i].replaceAll("^\\s+", "");
			   inputTmp[i] = inputTmp[i].replaceAll("\\s+$", "");
		   }
		   
	      return inputTmp;
	   }
	   
	   //获得学生们的信息(学号姓名)
	   public String[] getStudent() {

		   //去掉X等字符
		   input = input.replaceAll("#X:", "");
		   //把-换成空格方便拆分
		   input = input.replaceAll("-", " ");
		   //去掉开头结尾空格,防止数组分割多了
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   
		   String[] inputTmp = input.split("\\s+");
		   
	      return inputTmp;
	   }
	   
	   //获得删除的题号
	   public int getDelete() {

		   //去掉D,N等字符
		   input = input.replaceAll("#D:", "");
		   input = input.replaceAll("N-", "");
		   //去掉开头结尾空格,防止在转整数的时候还有空格导致出错
		   input = input.replaceAll("^\\s+", "");
		   input = input.replaceAll("\\s+$", "");
		   
		   int inputTmp = Integer.parseInt(input);
		   
	      return inputTmp;
	   }
	   
	   public String getInput() {
		   return input;
	   }
	   
	   public void setInput(String input) {
		   this.input = input;
	   }
  1. Controller类新增删除题目的方法

    先遍历查找是否有这道题,再进行修改;

    这里取了个巧,删除不是直接删除题目,而是把这道题的题目改成了“invalid”,这样在判断题目结果的时候和查找题目的时候可以通过判断题目内容判断题目状态,便于输出对应的结果;

	   //获取删除的题目并删除
	   public void deleteProblem() {
		   int deleteNum = input.getDelete();
		   
		   //遍历查找删除的题目是否存在
		   int hasFound = -1;
		   for(int k = 0;k < problemsTmp.getProblemsNum(); k ++) {
			   if(problemsTmp.getProblems().get(k).getNum() == deleteNum) {
				   hasFound = k;
				   break;
			   }
		   }
		   
		   
		   //找到后删除题目集中的这道题,并且在已经创建的试卷中搜索并修改
		   if(hasFound >= 0) {
			   
			   problemsTmp.getProblems().remove(hasFound);
			   //同时要修改problemsTmp中的problemsNum!
			   problemsTmp.setProblemsNum(problemsTmp.getProblemsNum() - 1);
			   
			   for(int k = 0;k < papers.getPaperNum();k ++) {
				   for(int j = 0;j < papers.getPapers().get(k).getProblems().size();j ++) {
					   if(papers.getPapers().get(k).getProblems().get(j).getNum() == deleteNum) {
						   papers.getPapers().get(k).getProblems().get(j).setQuestion("invalid");
						   //在删除后,要对本张试卷的总分进行修改!!!
						   //这样删除后如果分数不满100才会输出警告!!!
						   papers.getPapers().get(k).setScoreAll(papers.getPapers().get(k).getScoreAll() - papers.getPapers().get(k).getProblems().get(j).getScore());
						   papers.getPapers().get(k).getProblems().get(j).setScore(0);
//						   //同时要对本章试卷中的题目总数(problemsNum)修改!
//						   papers.getPapers().get(k).setProblemsNum(papers.getPapers().get(k).getProblemsNum() - 1);
					   }
				   }
			   }
		   }
		   
	   }
  1. 修改创建答卷的方法,前述特判在此体现

    在没有这道题时,依旧添加一个problem对象,但问题是“non-exist”,也是和前面一样取了个巧

	 //创建答卷
	   public void inputAnswerPaper() {
		   AnswerPaper answerPaperTmp = new AnswerPaper();
		   
		   String[] answersTmp = input.getAnswer();
		   
		 //第一位包括试卷编号和学号
		   String[] numsTmp = answersTmp[0].split("\\s+");
		   answerPaperTmp.setNum(Integer.parseInt(numsTmp[0]));
		   answerPaperTmp.setStudentID(numsTmp[1]);
		   
		   for(int i = 1;i < answersTmp.length;i += 2) {
			   //题号从1开始,放入答案和题目试卷编号,更新个数(在input时就更新了)
			   //此时的第二个参数表示的是这个答案回答的题目序号(在paper中的次序!!!而不是实际题目编号)
			   
			   int paperNumTmp = Integer.parseInt(numsTmp[0]);
			   int probelmNumTmp = Integer.parseInt(answersTmp[i]);
			   String answerTmp1 = "";
			   //若回答的最后一道题的答案为空,无论怎样的正则表达式都没法分出来,导致数组大小比正常小1,所以在此进行判断
			   if(i + 1 < answersTmp.length) {
				   answerTmp1 = answersTmp[i + 1];
			   }
			   
			   Answer answerTmp = new Answer(paperNumTmp, probelmNumTmp, answerTmp1);
			    answerPaperTmp.inputAnswer(answerTmp);
		   }
		   
		   answerPapers.inputAnswer(answerPaperTmp);
	   }
  1. 创建学生
	   //创建学生并放入Class
	   public void inputStudents() {
		   String[] studentsTmp = input.getStudent();
		   
		   //遍历studentsTmp,偶数位是学号,奇数是对应学生
		   for(int i = 0; i < studentsTmp.length; i += 2) {
			   Student studentTmp = new Student(studentsTmp[i + 1], studentsTmp[i]);
			   class1.inputStudent(studentTmp);
		   }
		   
	   }
  1. 返回错误信息:
	   //对于错误输入的返回
	   public void showWrongFormat() {
		   System.out.println("wrong format:" + input.getInput());
	   }
  1. judge判断并输出

    这次这里以答卷对应的试卷题目数量等遍历判断,先遍历查找是否作答,输出题目回答和正误,再遍历查找学生信息并输出,最后再遍历一边回答输出分数

	   //对输入的答卷依次判断并输出
	   public void judge() {
		   
		   for(int i = 0;i < answerPapers.getAnswerspaperNum();i ++) {
			   
			   //先判断是否有这张试卷
			   int hasFound = -1;
			   for(int k = 0;k < papers.getPaperNum();k ++) {
				   if(papers.getPapers().get(k).getNum() == answerPapers.getAnswerPapers().get(i).getNum()) {
					   hasFound = k;
					   break;
				   }
			   }
			   
			   
			   //找到这张试卷后,再寻找对应的学生是否存在
			   if(hasFound >= 0) {
				   
				   int hasFound2 = -1;
				   
				   for(int k = 0;k < class1.getStudentNum();k ++) {
					   //还是那个问题,此处比较String相等只能用equal!!!
					   if(class1.getStudents().get(k).getStudentID().equals(answerPapers.getAnswerPapers().get(i).getStudentID())) {
						   hasFound2 = k;
						   break;
					   }
				   }
				   
				   //不论有没有找到学生信息,只要卷子存在,就正常输出题目和回答
				   //这里之前因为删除题目时problemsNum没有修改出错了!
				   for(int k = 0;k < papers.getPapers().get(hasFound).getProblemsNum();k ++) {
					   //这里的参数要修改,第二个是试卷上本题的实际题号(在problemsTmp中的)//不对,不用改!应该改方法内部的
					   System.out.println(outputAnswer(hasFound, k, i, k));
				   }
				   
				   
				   //如果找到学生信息,输出得分
				   if(hasFound2 >= 0) {
					   
					   System.out.print(class1.getStudents().get(hasFound2).getStudentID() + " " + class1.getStudents().get(hasFound2).getName() + ": ");

					   //再次遍历得获得分数
					   int scoreAll = 0;
					   for(int k = 0;k < papers.getPapers().get(hasFound).getProblemsNum();k ++) {
						   int scoreTmp =  outputScore(hasFound, k, i, k);
						   scoreAll += scoreTmp;
						   System.out.print(scoreTmp);
						   if(k != papers.getPapers().get(hasFound).getProblemsNum() - 1) 
							   System.out.print(" ");
					   }
					   System.out.println("~" + scoreAll);
					   
				   }
				   //没找到学生信息就报错
				   else {
					   System.out.println(answerPapers.getAnswerPapers().get(i).getStudentID() + " not found");
				   }
				   
			   }
			   //没找到这张试卷就报错,不再向下判断
			   else {
				   System.out.println("The test paper number does not exist");
			   }
		   }
		   
	   }
	   
	   //输出题目和答案等(返回字符串),要对卷子中的每个题在答卷中进行搜索,看看有没有回答这道题
	   public String outputAnswer(int paperNum,int problemNum,int answerPaperNum,int answerNum) {
		   //参数中的problemNum是本题在这张试卷中的位置!需要在查找时换成实际的题号(在problemsTmp中的)
		   //上述说明不对,要用这里的problemNum(本题目在试卷中的位置)和每一个answer中的problemNum(也是在试卷中的次序,但是要减一)来比较!
		   
		   
		   //这段题目描述不清楚,应该先找题目存不存在?
		   //不是,还是先找本题有没有被回答!要找
		   
		   //找本题是否被作答
		   int hasFound = -1;
		   for(int i = 0;i < answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().size(); i ++) {
			   //此处前一个是在试卷中的次序,需要-1!
			   if(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(i).getProblemNum() - 1 == problemNum/*papers.getPapers().get(paperNum).getProblems().get(problemNum).getNum()*/) {
				   hasFound = i;
				   break;
			   }
		   }
		   
		   //如果找到了,先判断这个题目存不存在(被删除或者引用错误),然后再看是否回答正确了
		   if(hasFound >= 0) {
			   
			   //题目删除和引用错误不会同时出现,所以要逐个判断
			   //这里的problemNum还是位次!(在paper中按顺序一个个判断)
			   if(papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion().equals("non-existent")) {
				   return "non-existent question~0";
			   }
			   else if(papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion().equals("invalid")) {
				   return "the question " + (problemNum + 1) /*papers.getPapers().get(paperNum).getProblems().get(problemNum).getNum()*/ + " invalid~0";
			   }
//			   //因为在输入回答时(输入并创建答卷时)答案可能为空,这里还要再判断答案是不是空的,若是,按没有回答处理
//			   else if(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(hasFound).getAnswer().equals("")) {
//				   return "answer is null";
//			   }
			   else {
				   if(papers.getPapers().get(paperNum).getProblems().get(problemNum).judgeAnswer(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(hasFound).getAnswer())) {
					   return "" + papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion() + "~" + 
							   answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(hasFound).getAnswer() + "~true";
				   }
				   else {
					   return "" + papers.getPapers().get(paperNum).getProblems().get(problemNum).getQuestion() + "~" + 
							   answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(hasFound).getAnswer() + "~false";
				   }
			   }
			   
		   }
		   //没找到返回答案为空
		   else {
			   return "answer is null";
		   }
		  
		   
		   
	   }
	   
	   
	 //判断分数,返回分数
	   public int outputScore(int paperNum,int problemNum,int answerPaperNum,int answerNum) {
		   // 和输出答案正误的方法一样,这里的参数problemNum是本题在试卷中的位次,需要找到答卷中对应位次的回答
		   // 找到后比较,找不到返回0
		   
		   //找本题是否被作答
		   int hasFound = -1;
		   for(int i = 0;i < answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().size(); i ++) {
			   //此处前一个是在试卷中的次序,需要-1!
			   if(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(i).getProblemNum() - 1 == problemNum) {
				   hasFound = i;
				   break;
			   }
		   }
		   
		   if(hasFound >= 0) {
			   if(papers.getPapers().get(paperNum).getProblems().get(problemNum).judgeAnswer(answerPapers.getAnswerPapers().get(answerPaperNum).getAnswers().get(hasFound).getAnswer())) {
				   return papers.getPapers().get(paperNum).getProblems().get(problemNum).getScore();
			   }
			   else {
				   return 0;
			   }
		   }
		   else {
			   return 0;
		   }
		   
		   
	   }

​   

  1. 其余大体上与上次相似,但仍有诸多小细节需要修改

    ​  

  2. 完整源代码见 NCHU-OOP课程与PTA一到三周作业源代码 - cdz_hy - 博客园 (cnblogs.com)

​  

代码分析:

​  对本次代码用Source Monitor进行分析:

需要添加的图片

​  此次的程序显然比上次更复杂,可以得知代码共982行,410个语句,分为10个类,平均9.3个方法,方法平均语句数4.13;

​  从左侧图可以看出代码平均复杂度还是略低,平均语句和方法数略少;

​  不过为了让我在多次修改中能看懂我的代码,增加可读性,此次还是写了一定量的注释,注释数量较合适;

​  圈复杂度小于等于5,集中在1到2,最复杂的方法依旧是主方法;还是因为判断的输入类型多了,switch更复杂;

​  

踩坑总结:

​  本题让我印象非常深刻;一方面是复杂的程序,另一方面是一遍遍地改来改去让我红温了的感受;
​  

  1. 对于正则表达式的掌握不熟练,应用也不对;

​   唉,做了这么多次正则查找的题,现在还是这种半瓶水晃荡的感觉,挺丢人,挺不甘心的

​  最开始一版的正则表达式没有考虑到回答和题目答案可能为空或者是一个带空格的句子;

​  但是在改完为空或者是一个句子后还是过不了,以为是逻辑问题,反复修改,但最后发现还是正则的问题,可能宽松了一些,同时也有些按格式写的输入可能匹配不上,导致最后几个测试点一直过不了;

​  在改完之后才好不容易解决;

​   另外关于正则的问题还有一个方面;
​  
​  在对有效信息的获取时,我只用了split来分割,但其实这个时候用Pattern和Matcher类还有正则中的正向侦察模式(?<=exp)等更加合适;
​  

  1. 题目的描述有一定的问题

​  题目说到了乱序,但是没有说清楚到底乱序后各种输入的处理顺序到底是按照输入的顺序即时处理还是在所有输入结束后处理;比如题目在创建试卷的下方,到底该算non-exist还是正常放入;

​  题目没有说清楚,实际上这两种都可以通过所有测试点,直到最后一天的提示中才说到不考虑;
​  

  1. 总的来说,这道题花了我特别特别多的时间,在解决句子和空答案后的最后的几个测试点,开始以为是逻辑问题,改了足足一整天各种逻辑的排序(比如先收集好所有题目再放入试卷等各种能通过所有样例的逻辑可能),但都是最后那几个测试点有问题,让我非常烦闷;

​  后来实在改不下去了,看了看程序的别的地方,改了正则表达式,才发现原来一切的根源都是它!

​  

一道三周阶段总结:

关于自身:

​  经过前三周的oop课程学习和PTA的作业,切实感受到了这门课程的难度和课业压力;

​  Java和面向对象的设计能力无论怎么说,都会成为我们今后生活的饭碗,对这门课程,我们是不能马虎的;

​  同时,也在课程中学到了比较多的各方面的知识,学会了一些工具的使用,也对学习和各种事情有了不一样的见解;

​  后面要学的,困难的还多着;不管这三周的各方面怎么样,收拾好,继续前行吧!

​  

关于课程和作业:

​  关于课程,我觉得老师着重于程序设计的严格教学确实非常有意义,是切实对我们的设计和编程等的能力有很大帮助的;

​  但是关于作业,我觉得题目的一些逻辑关系和顺序尽量还是表达清楚一点,因为就我所知,oop3的最后一道,有的同学是先把题目收集完再组成试卷的,有的同学是在给出试卷信息的同时就即时处理了,两种都拿到了满分,但是明显这两种在程序的设计逻辑上是有较大区别的;

posted @ 2024-04-21 21:51  cdz_hy  阅读(60)  评论(0)    收藏  举报