BLOG_1
(1)前言:开学到现在一共进行了3次PTA大作业,今天是对这3次作业做一个总结。
1.第一次:第一次的题量是最大的,主要涉及的是简单的输入输出以及数据运算,相当与熟悉Java语言以及体验Java语言与标准C的不同。题目本身难度不大,但是数据判断做的太骚了点,有的题非常精确,有的题要比题目描述的更粗略才算正确,而有的题与题目描述的数据界定范围不一样,就是因为这个神奇的数据判定我估计很多人都对第一次大作业体验极差(doge)。而第一次写Java语言也有些生疏,其程序比C更加复杂,但有些功能确实让人眼前一亮。在某一题中我在进行浮点数运算时我忘记在分子中*1.0,但没想到系统自动运算出了浮点数结果,让我初步了解了Java语言相较于C的优势与进步。
2.第二次:第二次题目在题量与数据界定上都友好了许多,没有那么多另人懵逼的测试点,难度相比第一次有了提高,但体验却比第一次良好许多。第二次作业主要的知识点就是String类型了,相比于C繁琐的字符串,Java不管是声明、定义还是使用都方便许多,在使用时有着非常良好的体验感,其中String类的诸多有用的方法也给我们带来了很多便利以及减少了程序的复杂度,其中matches方法直接少了一个for的遍历,确实大开眼界。
3.第三次:第三次作业是最折磨的一次,难度非常大,代码量很多,错误格式等的判断也是十分严格,唯一还算友好的3题题量在这种难度下好像也不是那么的友好了。第三次作业我觉得不仅是对前2次考核的综合应用,前2次的知识点在第三次中被反复运用;更多的,我觉得第三次开始真正引入了”类与对象“的概念。第一题要主要要求我们判断输入数据的错误格式等的判定,对数据的使用只是顺便,而在第二、三题中,对数据的使用量骤然加大而且依旧要判断格式等问题。此时如果要再写一堆判定代码必然是繁重的工作任务,把第一次的判定代码做成一个方法(函数)显然是效率更高的方法,而对于Java来说,类的使用包含方法也高于方法,在此题目中完全可以建立一个格式类来判定格式。当然,我觉得就应用来说方法在本题目中已经足够,只是对类的使用更能加深我们对类的一点点理解。
(2)设计与分析:
1.第二次作业7-2:
7-2是第二次作业最难的一题,首先这个题目意思就有点懵一开始。思路方面,首先就是要把不需要校验的错误格式先行排除,前者是把长度小于11的排除,因为根据题目意思最少也要11为才成立;而第二种就比较巧妙,通过String的matches方法和正则表达式判定一个字符串是否全为一,避免了C语言用for遍历的繁琐。
而当某个情况被判定出后程序肯定是要结束的,可以用if和else来解决,但是首先if、else在这种情况下就不好用,其次多个if、else略显多余且使得程序不美观,所以,选择在每次成功的判断后加上return来结束程序,是个不错的选择。
而接下来的思路就很简单,从i=0开始循环,直到找到开始位”0“,开始进行各种情况的判定,此时我选择用j来获取开头的这一波字符串的起始位置,然后循环算出有效位中'1的数量以此来进行之后的效验码判断。
int sum=1; for(int i=0;i<a.length()-10;i++){ int num=0; if(a.charAt(i)=='0'){ for(int j=i+1;j<i+9;j++){ if(a.charAt(j)=='1') { num++; } } if(a.charAt(i+10)=='0'&&((num%2==0&&a.charAt(i+9)=='0')||(num%2==1&&a.charAt(i+9)=='1'))){ System.out.println(sum+":"+"validate error"); sum++; i=i+10; } else if(a.charAt(i+10)=='0'){ System.out.println(sum+":"+"validate error"); sum++; i=i+10; } else{ if((num%2==0&&a.charAt(i+9)=='1')||(num%2==1&&a.charAt(i+9)=='0')){ System.out.println(sum+":"+a.substring(i+1,i+9)); sum++; i=i+10; } else{ System.out.println(sum+":"+"parity check error"); sum++; i=i+10; } } } }
其中sum用来表示输出答案的序号,num用来计算有效位中”1“的数量,每次判断后i需要加上11(结尾加10for循环本身还加了1,当时没设计好),因为从起始位其一共要判断11位,这组结束后直接去下一组,而此处这种条件简洁明了的情况下,用if、else更能体现程序的结构性。
2.第三次作业7-1:直接上图说明。
if(s.length()<5){ System.out.println("Wrong Format"); return; } if(s.charAt(0)<'0'||s.charAt(0)>'9'){ System.out.println("Wrong Format"); return; } if(s.charAt(1)!=':'){ System.out.println("Wrong Format"); return; } if((s.charAt(2)<'0'||s.charAt(2)>'9')&&(s.charAt(3)<'0'||s.charAt(3)>'9')){ System.out.println("Wrong Format"); return; } for(int i=0;i<count1;i++){ if((s.charAt(c1[i]-1)<'0'||s.charAt(c1[i]-1)>'9')&&(s.charAt(c1[i]+1)<'0'||s.charAt(c1[i]+1)>'9')){ p=0; break; } if(i==count1-1){ if((s.substring(c1[i]+1).length()>1)&&(s.charAt(c1[i]+1)<'0'||s.charAt(c1[i]+1)>'9')&&(s.charAt(c1[i]+2)<'0'||s.charAt(c1[i]+2)>'9')){ p=0; break; } } else if(i<count1-1) if((s.charAt(c1[i]+1)<'0'||s.charAt(c1[i]+1)>'9')&&(s.charAt(c1[i]+2)<'0'||s.charAt(c1[i]+2)>'9')){ p=0; break; } } if(p==0){ System.out.println("Wrong Format"); return; } for(int i=0;i<count2;i++){ if((s.charAt(c2[i]-1)<'0'||s.charAt(c2[i]-1)>'9')&&(s.charAt(c2[i]+1)<'0'||s.charAt(c2[i]+1)>'9')){ q=0; break; } if((s.charAt(c2[i]+1)<'0'||s.charAt(c2[i]+1)>'9')&&(s.charAt(c2[i]+2)<'0'||s.charAt(c2[i]+2)>'9')){ q=0; break; } } if(q==0){ System.out.println("Wrong Format"); return; } if((Integer.parseInt(s.substring(0,1))+4)/2!=count1){ System.out.println("wrong number of points"); return; } if((Integer.parseInt(s.substring(0,1))+4)/2!=count2+1){ System.out.println("Wrong Format"); return; }
我的思路不是说一点点遍历看格式是否错误,而是直接得到逗号与空格的数量以及逗号和空格的位置(因为逗号数等于数据组数),通过逗号或者空格两侧元素的ASIC码值来初步判定判定是否正确以及数据数目是否符合要求。如果空格的两边都不是数字或者逗号的两边都不是数字那就格式错误;如果一个非数字符号之后紧跟一个非数字元素那就格式错误,如果String长度连最低要求都达不到,也是格式错误……但这些判定中有一部分其实有点蠢(不精确),比如5,5 +,5就判断不出来而5,5 +5,5就可以,诸如此类错误都不能判定,估计测试数也没有很精确,算是比较幸运。
tool=s; for(int i=0;tool.contains(",");i++){ if(i==0) c1[i]=tool.indexOf(","); else if(i>0) c1[i]=tool.indexOf(",")+s.length()-tool.length(); tool=tool.substring(tool.indexOf(",")+1); } tool=s; for(int i=0;tool.contains(" ");i++){ if(i==0) c2[i]=tool.indexOf(" "); else if(i>0) c2[i]=tool.indexOf(" ")+s.length()-tool.length(); tool=tool.substring(tool.indexOf(" ")+1); }
上面是记录空格、逗号位置的方法,先引入工具String类tool,避免改变原有输入s,然后不断找到字符串中第一个空格或逗号,再选取所找到字符后一位到结尾的一段新字符串赋值给tool,如此循环就能记录位置。
int count1=s.length()-s.replace(",","").length(); int count2=s.length()-s.replace(" ","").length();
这段代码是计算空格和逗号数量的,用s的长度减去把s中所求字符串换成空字符的字符串长度,去除以所求字符串长度即可得到所求字符串的数量,本题所求字符串也就是一个字符,长度为一。
X=s.substring(0,c1[0]); Y=s.substring(c1[0]+1,c2[0]); x[0]=Double.parseDouble(X); y[0]=Double.parseDouble(Y); if((X.length()>2&&X.charAt(1)=='0')||(X.length()==2&&X.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } if((Y.length()>2&&Y.charAt(1)=='0')||(Y.length()==2&&Y.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } for(int i=1;i<count2;i++){ X=s.substring(c2[i-1]+1,c1[i]); Y=s.substring(c1[i]+1,c2[i]); x[i]=Double.parseDouble(X); y[i]=Double.parseDouble(Y); if((X.length()>2&&X.charAt(1)=='0')||(X.length()==2&&X.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } if((Y.length()>2&&Y.charAt(1)=='0')||(Y.length()==2&&Y.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } } X=s.substring(c2[count2-1]+1,c1[count2]); Y=s.substring(c1[count2]+1); x[count2]=Double.parseDouble(X); y[count2]=Double.parseDouble(Y); if((X.length()>2&&X.charAt(1)=='0')||(X.length()==2&&X.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } if((Y.length()>2&&Y.charAt(1)=='0')||(Y.length()==2&&Y.charAt(0)=='0')){ System.out.println("Wrong Format"); return; } double reson=Math.sqrt((Math.pow((double)(x[0]-x[1]), 2.0)+Math.pow((double)(y[0]-y[1]), 2.0))*1.0); System.out.println(reson); in.close(); }
这里就是比较朴实的提取数字并且计算答案了,其中也夹杂了对"005"、"00"这种错误格式的判定。除去前两个和最后两个,每组都是空格到逗号间有一个,逗号到空格间又有一个,然后先用X或Y来存这段字符串,再判断这段字符串是否前两位都位非数字,通过后则用下x[i]或y[i]储存起来,用以计算2点距离。
2.第三次作业7-2:
判断格式错误与数据错误的方法和7-1差不多,多了一点前缀的判定,判定第一位是否是数字,判定第二位是否为冒号。但是我觉得格式判断比7-1精确了许多,7-1满分的格式判断在7-2不是满分,7-1那一点有些取巧的思路没经过优化在7-2直接是无了。
if(s.length()<5){ System.out.println("Wrong Format"); return; } if(s.charAt(0)<'0'||s.charAt(0)>'9'){ System.out.println("Wrong Format"); return; } if(s.charAt(1)!=':'){ System.out.println("Wrong Format"); return; }
7-1只用算2点距离,代码量小直接放到一起了,而7-2需要进行的运算很多,所以把运算单独放到了一个方法里。
public void line(String s){ double k=0,d=0,b=0; double x0=0,y0=0;//交点坐标 double k1,k2,b1,b2; int i,j; boolean bool,end=false; int count=(Integer.parseInt(s.substring(0,1))+4)/2; for(i=0;i<count-1;i++){ for(j=i+1;j<count;j++) if(x[i]==x[j]) if(y[i]==y[j]) end=true; if(end==true) break; } if(end==true) System.out.println("points coincide"); else{ switch(s.charAt(0)){ case '1': if(x[1]-x[0]==0) System.out.println("Slope does not exist"); else{ k=(y[1]-y[0])/(x[1]-x[0]); System.out.println(k); } break; case '2': if(x[1]-x[2]==0) d=Math.abs(x[0]-x[1]); else{ k=(y[1]-y[2])/(x[1]-x[2]); b=y[1]-k*x[1]; d=Math.abs(k*x[0]-y[0]+b)/Math.sqrt(k*k+1); } System.out.println(d); break; case '3': if((x[1]-x[0])*(y[2]-y[1])-(x[2]-x[1])*(y[1]-y[0])==0) System.out.println("true"); else System.out.println("false"); break; case '4': if((x[1]-x[0])*(y[3]-y[2])-(x[3]-x[2])*(y[1]-y[0])==0) System.out.println("true"); else System.out.println("false"); break; case '5': if(x[1]-x[0]==0&&x[3]-x[2]==0) System.out.println("is parallel lines,have no intersection point"); else if(x[1]-x[0]!=0&&x[3]-x[2]!=0) if((y[1]-y[0])/(x[1]-x[0])==(y[3]-y[2])/(x[3]-x[2])) System.out.println("is parallel lines,have no intersection point"); else{ k1=(y[1]-y[0])/(x[1]-x[0]); k2=(y[3]-y[2])/(x[3]-x[2]); b1=y[1]-k1*x[1]; b2=y[3]-k2*x[3]; x0=(b2-b1)/(k1-k2); y0=k1*x0+b1; if((x0>Math.min(x[0],x[1])&&x0<Math.max(x[0],x[1])&&y0>Math.min(y[0],y[1])&&y0<Math.max(y[0],y[1]))||(x0>Math.min(x[2],x[3])&&x0<Math.max(x[2],x[3])&&y0>Math.min(y[2],y[3])&&y0<Math.max(y[2],y[3]))) bool=true; else bool=false; System.out.println(x0+","+y0+" "+bool); } break; } } }
计算平行以及垂直关系都是用向量方法,分别是x1y2+x2y1=0以及x1x2+y1y2=0,此方法比计算斜率更好,避免了更可能出错的分情况讨论,也减少了运算量。而计算点与直线的距离则应用了公式|Ax^2+Bx+C|/sqrt(A*A+B*B),所以这里要算k,然后转化系数A、B、C。至于求两条直线的交点好像是有一个公式可以求,但我用的还是比较朴实的二元一次方程组,直接用x、y来表示k、b(A、B、C),然后通过交点x、y的位置判断是否在两条直线内。
for(i=0;i<count-1;i++){ for(j=i+1;j<count;j++) if(x[i]==x[j]) if(y[i]==y[j]) end=true; if(end==true) break; } if(end==true) System.out.println("points coincide");
在所有判定前,这段代码是找出是否存在任意两点重合,所有之后的判定都在这个if对应的else后。
3.第三次作业7-3:
因为前两个题搞的心态炸裂所以没写第三题,但是也有写思路,这里就不上代码直接说了。格式问题和数据问题与之前大同小异,想必判定数据精确度和7-2应该差不多,但还是比第一高。形状判断和重心之类的计算都是常规数学问题,直接带公式就行了,而判断是否是钝角三角形就计算三个角大小进行判断即可,重要的是第四点以及第五点。
第四点:判断直线是否经过三角形就是分别计算直线与三边是否有交点,有则算出坐标,在此之前必须要判断直线与边的所在直线是否重合。而交点等于2时分割出的图形必然有一块是三角形,既然是计算机,那么就完全可以使用海伦公式这种公式简单但运算量大的方法来计算大三角形面积和分割后小三角形面积,两者相减即可得出另一分割图形面积。
第五点:题目中已经有了提示,随便向一个方向发出射线然后再计算,但首先要判断点是否在边上。如果点不在边上,可以先视为一条直线,然后求交点,再看所求交点是否在初定方向的那一段以排除,用最后剩下的交点来判断点是否在三角形内部。
(3)踩坑心得:
1.第一次作业:我觉得第一次作业总体应该没有什么坑,实在是题目有点问题,如果按照题目意思进行边界判定就会出错,数据判定比题目精确度第反而才能对,如果这些算坑那真的是太坑了。一开始我还以为是数据界定特别精确或者严苛到具体数位,觉得这题目很***钻啊,后来发现只能说体验极差。
2.第二次作业:第二次作业感觉也没有很多坑,要说有的话那就是7-2中一定要注意一次是判定一组数据,所以i(假设使用i循环)要加11,直接判定下一组;如果i++就可能会出现2组数据5次判定5次输出。
3.第三次作业:这次的坑就多了,有一些地方我知道我踩了但就是不知到踩哪里了。首先这次的运算量就很大,情况也多,其次选的问题也是那种很容易漏解的问题,一不小心就容易漏掉某些答案,或者是if、else没有排除所有情况。
踩坑代码被修改了,就直接说明吧,比如判断平行就得考虑k无穷大,要单独列出来,还得考虑是否重合(第三题),再排除一堆情况,最后else出来的才是对的,做题时我就漏掉了k不存在这种情况。而k不存在这种情况在这种题型中非常喜欢考,比如说点到垂直x轴直线距离、判断平行、判断是否在同一直线上。做题是在k不存在这里踩的那是有点多。
踩的另一波坑就是关于交点是否在两条直线内的判定,一开始以为是&&的关系,后来经过样例测试才知道是||的关系,只要还在其中一条线段内就可。
(4)改进建议:
1.第一次真不知道怎么改了,数据太怪了。
2.第二次作业:
· 下面的包含上面的,所以可以合并为一次if判定。
3.第三次作业:
求交点有公式更加简单快捷;
涉及平行和垂直的计算运用向量来算更加方便;避免了繁复的情况判断;
求三角形面积知道三点坐标直接海伦公式,这不是在做数学题,合理运用计算机在计算上的高速可以简化许多问题。
(5)总结:
1.学到了什么:第一次作业让我初步熟悉了Java语法,熟悉了Java的输入输出计算等,但此时的Java还只是C换了层皮,本质没有多大不同;第二次则开始则加强了对String的了解与使用,感受到了String相比字符数组的进步与便利,而String类的各种方法也然我初步接触了对象与类;第三次我觉得就是开始加强对类或者方法的使用了,因为第一题的格式判断在后两题都要运用,估计是暗示我们把第一题的代码改良成一个类或方法以让后两题使用,但应为功能只有不多,所以更偏向方法的使用,而功能多起来时显然包装成类更加合适。
2.改进意见:这个题目本身我觉得判定数据要做好,不能出现第一次作业那种情况;
题目难度我觉得得合理一点,比如说7-3这个就太难了,数据运算多,考察的点也多,而且也不说明错误点,很难修改,到头来其实没学到啥,全在改数学错误;
我觉得题目应该更多地考察我们对于Java语言新知识的应用,对于这种数据或者数学问题没有必要抓这么紧,比如可以出点题目要求多个类之间的相互应用,类本身可以不那么复杂,但多个类的 使用肯定能让我们更多地了解到类的妙处;
实验的话希望要求表达地清晰一点,有的时候有点难以理解实验要求。