一、前言显而易见,此三次的PTA作业所考察的知识点呈现不断变大的趋势。PTA1主要考察JAVA语言的规范程度以及部分基础语句的用法,题量虽多但难度不大 ,考察的题型较为基础。PTA2则是在前者的基础上增加了一定的难度,检验编程的健壮程度以及我们考虑问题的完整全面性,同时也在考察了JAVA语言中的数组以及字符串等结构上要求写出稍微复杂的算法,题量以及难度适中。PTA3相对于前面两次作业则是一次巨大的变化,直接体现出了JAVA语言面向对象编程的本质,引进了类的构建实现等相关知识,前两题的难度较低,但第三题的难度则有了较大的提升,要求编程者对正则表达式有一定的掌握并设置了多种情况下的测试点,对编程者的自学能力以及代码的完整程度提出了较高的要求。
二、设计与分析PTA 1-8
7-8 判断三角形类型 (20 分) 输入三角形三条边,判断该三角形为什么类型的三角形。 输入格式: 在一行中输入三角形的三条边的值(实型数),可以用一个或多个空格或回车分隔,其中三条边的取值范围均为[1,200]。 输出格式: (1)如果输入数据非法,则输出“Wrong Format”; (2)如果输入数据合法,但三条边不能构成三角形,则输出“Not a triangle”; (3)如果输入数据合法且能够成等边三角形,则输出“Equilateral triangle”; (3)如果输入数据合法且能够成等腰直角三角形,则输出“Isosceles right-angled triangle”; (5)如果输入数据合法且能够成等腰三角形,则输出“Isosceles triangle”; (6)如果输入数据合法且能够成直角三角形,则输出“Right-angled triangle”; (7)如果输入数据合法且能够成一般三角形,则输出“General triangle”。 输入样例1: 在这里给出一组输入。例如: 50 50 50.0 结尾无空行 点击并拖拽以移动 输出样例1: 在这里给出相应的输出。例如: Equilateral triangle 结尾无空行
设计与分析
在此我们利用SourceMonitor对程序的源码进行分析,其给出的代码分析如下:
根据该软件所生成的报表,可以看到该软件一个亮点就是将相关指标进行了一定程度上的联合分析,并做出了雷达图与三维柱状图,将数据进行可视化,有利于我们更直观的获取数据特征,有时甚至更容易获取潜在的比较有价值的信息,帮助我们提高软件测试的效率(其实主要是静态测试的效率)。雷达图所呈现红色区域则是我们提交测试的代码在该软件上的测试结果,蓝色区域范围内则是代码较为合理的情况下所应产生的结果.
根据所生的雷达图我们可以看到:
1.由于此次代码的思路以及内容较为简单,因此我们未对其添加任何注释,所以该项在雷达图上处于一个较低的位置.
2.由于该代码主要使用if-else语句进行,该代码的平均以及最大圈复杂度达到了27,可见该代码的结构较为复杂且在日常的维护修改中的难度较大,雷达图中圈复杂度项明显超过了适宜范围,应当对该部分进行改进便于日后的维护以及使用.
3.AVG Stmts/Method方面,由于我们将诸多判断语句均置于一个函数之中,导致此项的数值较高,过高的平均每个函数包括的语句数目将会使得一个函数过于的臃肿且不易修改,因此应尽可能地对其进行改进以及缩略.
踩坑心得
1.输入值的类型
题目中所提到输入数据均为实型数,若想当然的运用int类型的数值进行输入显然不能通过所有的测试点,因此此处我们应该使用float/double类型进行增强代码的健壮性.
2.不同类型三角形的检验顺序
由题意得,我们需要对输入的三个实数型数字进行7种判断,程序的顺序是自上而下依次进行的,但部分不同类型三角形中会有一定的相同之处从而影响判断的进行.例如:等腰直角三角形同时也是一个等腰三角形,若只单纯检验是否存在两边相等进行判断,两项的执行顺序必然会影响最后的结果.同时直角三角形以及等腰直角三角形的判断亦是如此.这也侧面反映出了程序设计逻辑完整性的重要程度.
3.等腰直角三角形的判断
一开始死板的将"三边满足勾股定理且存在两边相等"作为判断此项三角形是否为等腰直角三角形的条件,后续发现该条件存在一定的问题使之不能通过测试点,后查阅资料将判断条件改为"相等的两边a b同时满足a^2+b^2-c^2>0.00001"即可通过测试.
改进建议:
针对该代码的if-else结构有如下改进方法:
1.提前return,减少else判断
2.使用三目运算符
3.使用switch case,同样,我们可以借鉴方式一提前return,来代替else
4.表驱动法.表驱动法是指我们可以将信息存在于表中,从表中取,而不必用if else逻辑去寻找,大可以将这里的“表”理解为一种容器。
例如我们可以将每月的天数存在数组中,需要时,直接从数组中获取,而不是用if else输出。
PTA 2-4
7-4 求下一天 (30 分)
输入年月日的值(均为整型数),输出该日期的下一天。 其中:年份的合法取值范围为[1820,2020] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31] 。 注意:不允许使用Java中和日期相关的类和方法。 要求:Main类中必须含有如下方法,签名如下: public static void main(String[] args);//主方法 public static boolean isLeapYear(int year) ;//判断year是否为闰年,返回boolean类型 public static boolean checkInputValidity(int year,int month,int day);//判断输入日期是否合法,返回布尔值 public static void nextDate(int year,int month,int day) ; //求输入日期的下一天 点击并拖拽以移动 输入格式: 在一行内输入年月日的值,均为整型数,可以 用一到多个空格或回车分隔。 输出格式: 当输入数据非法及输入日期不存在时,输出“Wrong Format”; 当输入日期合法,输出下一天,格式如下:Next date is:年-月-日 输入样例1: 在这里给出一组输入。例如: 2020 3 10 点击并拖拽以移动 结尾无空行 输出样例1: 在这里给出相应的输出。例如: Next date is:2020-3-11 点击并拖拽以移动 结尾无空行 输入样例2: 在这里给出一组输入。例如: 2025 2 10 结尾无空行 点击并拖拽以移动 输出样例2: 在这里给出相应的输出。例如: Wrong Format 结尾无空行
设计与分析:
由题意可知,在输入数据之前我们需要对输入的数据是否为整型数以及合法性进行判断,该题中我们注重了无非三点:1.是否为闰年 2.该月共30/31天 3.是否执行天数操作会同时进行月份/年份的操作.以上述三点为基础展开判断,在主方法中对下面三个类进行申明调用,按照checkInputValid()---isLeapYear()--nextDate()的大体流程进行程序设计可以使得设计过程事半功倍.下面我们使用SourceMonitor生成的报表内容进行分析,报表内容如下图所示:
显而易见,此次我们的SourceMonitor分析图的平均函数包括语句数以及最大/平均复杂度依然处在一个超出合理范围的位置,仔细分析其原因:
1.虽使用了类方法来简化封装各个部分的功能,但在nextDate()函数中我们依然看到采用了多个if-else语句来进行不同情况的判断,过多的if-else显然是不利于程序的可读性以及可维护性的,这在我们的代码编写中也能充分感受到,由于采用了较多个if-else语句,在未对其进行注释的情况下,我们很容易因为思路不够清晰致使判断条件发生错误或者是考虑不到的某个情况导致程序的结果发生错误.
2.此代码的最大函数深度处在一个合理的位置但平均函数深度却大大超出了合理范围,究其原因是因为我们在各个函数所使用的语句过于繁琐复杂,应当适当的对函数的语句进行合理的调整,使其深度降低降低代码阅读修改的难度.
踩坑心得:
1.在进行该题的代码编写时,首先便要仔细观察年份/月/日的取值是否有范围,想当然的认为上述几个元素取值是没有范围的显然是不合理的,同时也会导致程序不能通过检测,在面对较大的数时也容易因为数值类型的设置而崩溃出错.
2.在进行平/闰年的判断时,较容易因为对于平/闰年的判断不够清晰导致出错,从而影响整个程序的运行结果.正确的判断条件语句如下:
if((year%400==0)||(year%100!=0&&year%4==0))
3.在进行求各个日期的下一天时,我们需要对各个月份进行分类讨论,若日的数值在[1,27]范围之内则无太大难度,直接对日的数值进行加一即可.但在[28.31]范围之间则要特别考虑的平闰年对于2月份的影响,需要考虑该年的2月份的总天数以及是否需要对月份执行加一操作,其他月份则需要考虑该月份的天数为30/31天,并考虑是否应当执行月份加一的操作.
4.该题最让人意想不到的便是其跨年测试该测试点,大多数人在进行编程时通常不会考虑到此类情况,这也从另一个方面提醒了我们加强对程序代码健壮性的思考,应对题目中可能出现的任何情况进行正确的思考与i及分析.
改进建议:
1.通过对于SourceMonitor生成报表的分析,我们可以发现仅仅设立三个类进行程序的设计虽然能完成题目所给的基本要求,但在我们编写执行时仍存在多种判断交织在一起的情况,极大的影响了程序的质量以及正确性,因此我们可以再增设一个类对于根据所输入的月份以及日期数进行判断是否进行月份加1操作,同时也可以将是否跨年作为一个类来简化程序,两者均返回一个boolean值,此方法不仅简化了程序的代码,提高程序的美观程度,在保持正确率的情况下更加符合我们面向对象设计的特点,
2.对于函数中存在的大量if-else结构,我们肯定是需要对其进行简化修改的,由于该代码判断条件的复杂以及多样性,我认为在众多的改进方法中应当选择提前return的方法对代码进行修改,这样的修改对代码的质量以及可读性可维护性无疑提升不少,下面我们针对该种修改方法给出一个例子帮助大家理解:
private int handlePre1(boolean flag) { if (flag) { //do something } else { //do something return -1; } return 0; }
修改后:
private int handleAfter1(boolean flag) { if (!flag) { //do something return -1; } //do something return 0; }
PTA 2-5
7-5 求前N天 (30 分) 输入年月日的值(均为整型数),同时输入一个取值范围在[-10,10] 之间的整型数n,输出该日期的前n天(当n > 0时)、该日期的后n天(当n<0时)。 其中年份取值范围为 [1820,2020] ,月份取值范围为[1,12] ,日期取值范围为[1,31] 。 注意:不允许使用Java中任何与日期有关的类或方法。 输入格式: 在一行中输入年月日的值以及n的值,可以用一个或多个空格或回车分隔。 输出格式: 当输入的年、月、日以及n的值非法时,输出“Wrong Format”; 当输入数据合法时,输出“n days ago is:年-月-日” 输入样例1: 在这里给出一组输入。例如: 2018 6 19 8 结尾无空行 输出样例1: 在这里给出相应的输出。例如: 8 days ago is:2018-6-11 结尾无空行 输入样例2: 在这里给出一组输入。例如: 2018 6 19 -8 结尾无空行 输出样例2: 在这里给出相应的输出。例如: -8 days ago is:2018-6-27 结尾无空行
设计与分析
显然,通过对此题题目的观察,我们可以发现该题就是逆向思维的7-4升级版,唯一不同的便是其在上题的基础上将改变的天数由1改成了一个变量n,因此我们解决此题的大致思路依然是不变的,甚至可以将上题的代码进行借鉴修改从而得到正确的结果.下面我们对提交的代码使用SourceMonitor进行分析,其生成的报表内容如下图所示:
可以看到,此次代码产生的图像和上题产生的雷达图基本一致,都是在最大/平均圈复杂度以及平均/函数深度方面不在一个较为合理的范围之内,由于我们是在上一题代码的基础上对其进行的修改,并且两题的思路基本差不多.因为两个图的数值特点相似也是情理之中,对此我们不再对其进行具体分析,大致分析可以借鉴上题的该内容部分,此处再及进行分析无疑重复啰嗦了.
踩坑心得:
1.此题是在上题的基础上进行修改的,但唯一不同的地方则是该题需要我们控制n在[-10,10]的范围内对日期进行回溯,无疑需要我们对更多的情况进行考虑,因此能否将各种情况考虑完全是解决本题的关键.
2.与上题一样,在进行该题的判断时我们首先便要控制平/闰年来确定2月份的天数,同时考虑到各个月份的总天数,根据天数的不同进行基础设计.
3.该题由于n值可为正和负,因此我们需将天数为[11,19]之间的以及[1.10],[20,30/31]进行分类讨论(2月另外进行讨论),对[11,19]区间内的数值直接执行相减的操作,而对于其他区间的则注意月份的加减操作.
4.因为平/闰年的影响导致2,3月份在进行月份的加减时需要多加进行考虑.
5.此题最大的坑也是在于跨年操作该点,因此我们需要对12月日期区间为[22,31]以及1月份[1-9]的情况的进行特别讨论,在执行完日期月份的加减操作后也不能忘记对年份进行相应的加减操作.
改进建议:
1.对于此题所产生的if-else语句,我们可以对加使用三目运算符进行简化操作,其操作例子如下:
private int handlePre2(boolean flag) { if (flag) { return 0; } else { return 1; } }
优化后:
private int handleAfter2(boolean flag) { return flag ? 0 : 1; }
这种方式局限性比较大,但对于那些依据状态直接返回某个值的情况非常适用,优化后代码非常简洁。如果三目运算符中还嵌套一层三目运算符,建议就不要使用这种方式了。
2.新创建一个对月份减一/加一进行判断的类,在输入各项数值并通过合法性检验后直接调用此类,同时再创建一个判断各个天数的类,根据此类的结果作为月份加减操作的判定条件,这样可以使得程序最大限度的得到了简化,多个类将功能更好的展现在了用户的面前,更加符合面向对象的特点.
PTA 3-2
7-2 定义日期类 (28 分) 定义一个类Date,包含三个私有属性年(year)、月(month)、日(day),均为整型数,其中:年份的合法取值范围为[1900,2000] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31] 。 注意:不允许使用Java中和日期相关的类和方法,否则按0分处理。 要求:Date类结构如下图所示: 类图.jpg 输入格式: 在一行内输入年月日的值,均为整型数,可以用一到多个空格或回车分隔。 输出格式: 当输入数据非法及输入日期不存在时,输出“Date Format is Wrong”; 当输入日期合法,输出下一天,格式如下:Next day is:年-月-日 输入样例1: 在这里给出一组输入。例如: 1912 12 25 结尾无空行 输出样例1: 在这里给出相应的输出。例如: Next day is:1912-12-26 结尾无空行 输入样例2: 在这里给出一组输入。例如: 2001 2 30 结尾无空行 输出样例2: 在这里给出相应的输出。例如: Date Format is Wrong 结尾无空行
设计与分析
该题与题2-4仍有极大的相似之处,不同的则是该题要求建立一个给定了结构的Date类,其具体结构如下图所示:
在用户输入了三个元素的值后,通过Date类进行传输,使用Date类中的函数getyear/day/month对数值进行初步构造,再利用chenckinputvalidity()函数给出合法性的判断以及平/闰年的boolean值,通过合法性检验后通过setDay/Year/Month最后通过getNextDate()返回对应的下一天值.使用SourceMonitor对代码进行分析,其分析结果如下图:
1.首先观察提交代码的平均/最大圈复杂度,我们可以发现由于我们在判断是否平/闰年,该月份是否为30/31天,2月份共有28/29天时,是/否跨年/月时均采用的if-else结构进行的判断,因此圈复杂度过高在所难免.
2.在函数深度方面,由于代码的判断语句均挤在一个函数之中,因此导致函数的深度较高 ,这也侧面说明了函数可能存在重复判断或者是存在某些无意义的语句可以进行删除或者修改操作,
踩坑心得:
其实重复做了两题跟此题差不多的题型,对于各个测试点应该都是应该有所知晓的.
1.首先便是老生常谈的是否跨年问题,这个问题我们直接在setday()根据对应的月份数进行相应的判断即可,当然也不能忘记最后对年份进行修改.
2.再者便是对于各个月是否为30/31月份临界判断以及操作.
3.最后则是对于平/闰年2月份的月末进行判断以及操作.
改进建议:
1.从上两次题到现在这题,虽然题目以及解决方法基本没有发生变化,但是我们可以明显感觉到我们在编写代码时发生的变化,从面向过程逐渐向面向对象演变,程序更加具有层次感,提高了程序的可读性.
2.对于过多的if-else问题,我们可以尝试使用工厂模式+策略模式进行改变,之后每输入一个数值,只要实现Profession接口就好,并在ProfessionFactory中注册自己。使用策略模式+工厂模式,再也不需要为日益增长的if else头疼了。其具体例子如下:
String profession = "student"; if ("student".equals(profession)) { System.out.println("学习"); } else if ("teacher".equals(profession)) { System.out.println("教书"); } else if ("programmer".equals(profession)) { System.out.println("写代码"); } //.......以后可能会加上各种各样的职业
优化后:
职业窗口:
public interface Profession { //输出职责 void output(); }
实现类:
public class Student implements Profession { @Override public void output() { System.out.println("学习"); } } public class Teacher implements Profession { @Override public void output() { System.out.println("教书"); } } public class Programmer implements Profession { @Override public void output() { System.out.println("写代码"); } }
工厂类:
public class ProfessionFactory { private static Map<String, Profession> map = new HashMap<>(); //未知职业 private static final Profession DEFAULT_PROFESSION = new DefaultProfession(); static { map.put("student", new Student()); map.put("teacher", new Teacher()); map.put("default", DEFAULT_PROFESSION); } public static Profession getProfession(String s) { Profession profession = map.get(s); return profession == null ? DEFAULT_PROFESSION : profession; } static class DefaultProfession implements Profession { @Override public void output() { System.out.println("职责未知"); } } }
Profession profession = ProfessionFactory.getProfession("student");
profession.output();
PTA 3.3
7-3 一元多项式求导(类设计) (50 分) 编写程序性,实现对简单多项式的导函数进行求解。详见作业指导书。 OO作业3-3题目说明.pdf 输入格式: 在一行内输入一个待计算导函数的表达式,以回车符结束。 输出格式: 如果输入表达式不符合上述表达式基本规则,则输出“Wrong Format”。 如果输入合法,则在一行内正常输出该表达式的导函数,注意以下几点: 结果不需要排序,也不需要化简; 当某一项为“0”时,则该项不需要显示,但如果整个导函数结果为“0”时,则显示为“0”; 当输出结果第一项系数符号为“+”时,不输出“+”; 当指数符号为“+”时,不输出“+”; 当指数值为“0”时,则不需要输出“x^0”,只需要输出其系数即可。 输出格式见输入输出示例。 输入样例1: 在这里给出一组输入。例如: -2* x^-2+ 5*x^12-4*x+ 12 结尾无空行 输出样例1: 在这里给出相应的输出。例如: 4*x^-3+60*x^11-4 结尾无空行 输入样例2: 在这里给出一组输入。例如: 2*x^6-0*x^7+5 结尾无空行 输出样例2: 在这里给出相应的输出。例如: Wrong Format 结尾无空行
设计与分析:
首先给出SourceMonitor的分析报表:
1.由上图可见,此次代码在注释这方面处于一个不合理的位置,这是因为该题需要考虑的情况较为多且复杂,从而使用了大量的注释来对各种情况进行了分开阐述.但过多的注释虽然可以使编程的思路更为清晰,同时也影响了用户的阅读体验.
2.此次代码的圈复杂度较低,这是因为吸取了之前if-else运用过多致使圈复杂度过于高的教训,在代码的编写过程种使用提前return的办法减少了if-else的数量,圈复杂度立竿见影的下降.
3.由于各个函数包含的内容依然较多,导致函数深度指标依然处于一个不是特别好的范围之中.
踩坑心得:
1.该题目作为这三次作业中难度最高的一题,极大地考验了我们的自学能力以及编程思想,我们在做题之前仔细的研读正则表达式的使用方法以及与其相关的各种函数操作,方可达到可以探索此题的标准.
2.该题中有一个测试点为大数则是,在尝试了doule,long int后依旧报错后,只能选择头文件为import java.util.BigIntegar的方法进行代码的编写.
3.此题的最后一个测试点就稍微有点离谱了,例如3*x+4*x需要输出3+4而不是7,这个测试点也是让我没想到的,通过查阅了大量有关资料才发现了这个问题,并进行了代码的修正.
4.此题最前面的系数若为+号的话是不需要输出的,在编程中需要特别注意.
改进建议:
1.此题我认为正则表达式已经是解决其的最好办法,故在算法思路上不做出改进.
2.针对函数过于庞大的缺点,我们可以将一个函数在不影响其正常功能的情况下将其进行拆解成多个小函数以达到降低函数深度的目的.
3.部分字符串的处理可以运用封装好的函数进行操作,以达到简化代码,事半功倍的效果.
总结
通过这三次的PTA的训练,基本掌握了JAVA的编程风格以及基础语句,也逐渐从面向过程的思路转为面向对象,对于面向对象程序设计拥有了更深层次的理解,在对于类构建以及使用方面更加的得心应手,
但在这三次的训练之中也发现了不少问题,比如说对于某些算法仍然不是很熟悉,同时在使用字符串的某些封装函数时容易出错,编程的速度还是较慢,这些都是今后我需要进行学习以及训练来加强的.对于当前的授课方式,我认为大部分没有问题,但是在课堂代码实践所花费的实践可能过多了,我个人认为应该多结合实例及知识点进行讲解.相信自己能在JAVA学习上取得更大的进步.