引言:java的课程进入尾声,但是编程的路才刚开始。
前言:这三周的大作业主要考察各个类之间的关系,数据的封装,类的继承,多态,接口,抽象类,集合框架等多个知识的综合运用。 自学正则表达式的使用,有些题目对于格式的判断非常的复杂,使用正则表达有效判断了输入的合法性,并且减少了大量的格式判断代码。 通过三次大作业写完电信计费问题,这样的方式相比之前的点线型友好了太多,这样就又充分的时间去书写代码,完成代码的内容。 三次大作业彼此关联难度又呈持平的状态,给我们编码时提供了莫大的信心。 PTA大作业6 各题目 设计与分析 踩坑心得 改进意见 核心代码分析:(1)7-1 电信计费系列1-座机计费
设计与分析:
类图如下:
此题的难度在于不好下手,做此类图较为复杂的题,需要仔细观察类图,明确类图之间的联系
在此题中 User类中的userRecord是这道题目的突破口,在这个userRecord中可以把用户的通话记录数据放入,便于Chargemode在中间的调用。
然后再去理解类图中的chargeMode类,这是这个程序的核心代码,通过这一段计算出用户的花费,完成程序。
虽然类图看起来非常的复杂,但只要将代码分解,先从user入手,自然就写到了userRecord,接着就补充到chargemode,再然后就去构思main函数,获取输入数据即可
踩坑心得:
这应该是我最重要的一部分代码,看起来非常的简单,但得来是十分不容易的。
这道题目涉及到对arrayList排序的问题。我开始是先声明一个User user[]的对象数组,先历便arrayList,将数据赋值给user[],
再用compareTo方法对字符串进行冒泡排序,但是排序之后就出现了一个问题,我通过user数组赋值没有办法删除重复的元素,这就导致输出重复。
虽然最后解决了重复的问题,但是这种方法效率比较低,我又找到了以上方法。
在把数据比较入arrayList时进行,如果arratList的user.numble值比后一个要大,那么用j记录下arrayList中的位置,再用指定位置插入,将新进来的数字插入进来,这样插入完成后就已经是有序的了。
改进意见:
1.由上图可知,这个代码的圈复杂度达到了19,这说明代码的质量不高。因为我在写题目的过程中,只注意需要完成题目要求的功能,而没有对代码进行优化,导致写了过多的if/else语句,据查找资料,if/else 和循环的使用会使得圈复杂度提高,改进的方法是将if/else语句换成switch语句,可以有效减少圈复杂度。
2.可以将判断的逻辑颠倒过来,这样代码的思路就会更加的清晰,代码的可读性也会更高。
3.可以将arrayList换成hashset,用集合简化题目。
核心代码分析:
① 数据判断的正则表达式:
final String regex = "^u-[0-9]{11,12} 0$"; final String regex0 = "^t-[0]{1}[0-9]{9,11} [0]{1}[0-9]{9,11} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; final String regex00 = "^079[0-9]$";
②arrayList的排序算法:
while(!s.equals("end")) { if(Pattern.matches(regex, s)) { String s0 = s.substring(2); String str[] = s0.split("[ ]"); int flag=0; User user = new User(); user.setNumber(str[0]); for(User i:list){ if(i.getNumber().equals(user.getNumber())) flag=1; } int j=0; for(User m:list) { if(m.getNumber().compareTo(user.getNumber())>0) break; j++; } if(flag==0){ list.add(j,user); } }
7—2电信计费系列2-手机+座机计费 各题目 设计与分析 踩坑心得 改进意见 核心代码分析:
(1)7-2 串口字符解析
设计与分析:
类图如下(其他与上一题基本相同)
此题的难点在于对于手机来说:拨打电话和接听电话都需要计费,不但需要判断是拨打还是接听,还需要判断所在的位置。
对于接听来说,所在的位置如果发生变化,相应的计费金额也会有所改变。
核心就来到了判断输入是否合法,输入的时间是否合法等问题,其意思是说如过无法判断输入数据的准确与否,就无法写出这道题目。
我同样采取了正则表达式判断时间,对于本题给出的格式yyyy-MM-dd HH:mm:ss。我写了一个简易的时间正则表达式。
在获得输入的数据后需要了解计费规则的相关类,
这些类的核心方法是: calCost(ArrayList<CallRecord> callRecords)。 该方法针根据输入参数callRecords中的所有记录计算某用户的某一项费用;
如市话费。 输入参数callRecords的约束条件:必须是某一个用户的符合计费规则要求的所有记录。 SendMessageRule是发送短信的计费规则类,用于计算发送短信的费用。
踩坑心得:
这一部分的代码就是五个部分的判断,对于题目给出的五个地区不同的区号判断。
只需要从地区范围由小到大判断,就不会遗漏数据了。
改进意见:
1、由上图分析,这道题的圈复杂度为70,超过了15,这表明代码的质量还可以提高,这道题是由于写了很多个循环的结果导致。其实有些循环可以把他合并起来,这样既减少了代码的行数,又提高了代码的质量。
2、这道题存在有些代码重复率过高的问题,可以通过静态方法的调用,解决这些代码的重复问题。
核心代码分析:
①时间的正则表达式:
final String landtel = "t-[0]{1}[0-9]{9,11} 1[0-9]{10} [0-9]{3,4} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; // 座机--电话 final String teltel = "t-1[0-9]{10} [0-9]{3,4} 1[0-9]{10} [0-9]{3,4} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; final String telland = "t-1[0-9]{10} [0-9]{3,4} 0[0-9]{9,11} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$";
②arrayList的使用:
if (Pattern.matches(regextel, s)) { String s0 = s.substring(2); String str[] = s0.split("[ ]"); int flag = 0; User user = new User(); user.setNumber(str[0]); for (User i : listtel) { if (i.getNumber().equals(user.getNumber())) flag = 1; } int j = 0; for (User m : listtel) { if (m.getNumber().compareTo(user.getNumber()) > 0) break; j++; } if (flag == 0) { listtel.add(j, user); } } } else if (Pattern.matches(regex0, s) || Pattern.matches(landtel, s)) {// 座机通话记录 座机打座机 // 增加座机打手机 if (Pattern.matches(regex0, s)) { UserRecords ue = new UserRecords(); CallRecord callRecord = new CallRecord(); String str[] = s.split("[ ]"); String d1 = str[2] + " " + str[3]; String d2 = str[4] + " " + str[5]; Date da1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").parse(d1); Date da2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").parse(d2); callRecord.setCallingNumber(str[0].substring(2)); callRecord.setCallingAddressAreaCode(str[0].substring(2, 6)); callRecord.setAnswerNumber(str[1]); callRecord.setAnswerAddressAreaCode(str[1].substring(0, 4)); callRecord.setStartTime(da1); callRecord.setEndTime(da2); for (User j : list) { if (j.getNumber().equals(callRecord.getCallingNumber())) { ue = j.getUserRecords(); } else continue; if (callRecord.getAnswerAddressAreaCode().equals(callRecord.getCallingAddressAreaCode())) { // 判断地区 ue.addCallingInCityRecords(callRecord); } else if (callRecord.getAnswerAddressAreaCode().equals("0701") || Pattern.matches(regex00, callRecord.getAnswerAddressAreaCode())) { ue.addCallingInProvinceRecords(callRecord); } else ue.addCallingInLandRecords(callRecord); j.setUserRecords(ue); break; } } if (Pattern.matches(landtel, s)) { UserRecords ue = new UserRecords(); UserRecords ue0 = new UserRecords(); CallRecord callRecord = new CallRecord(); s = s.substring(2); String str[] = s.split("[ ]"); String d1 = str[3] + " " + str[4]; String d2 = str[5] + " " + str[6]; Date da1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").parse(d1); Date da2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").parse(d2); callRecord.setCallingNumber(str[0]); callRecord.setCallingAddressAreaCode(str[0].substring(0,4)); callRecord.setAnswerNumber(str[1]); callRecord.setAnswerAddressAreaCode(str[2]); callRecord.setStartTime(da1); callRecord.setEndTime(da2); // 分区域放进去 for (User j : list) { if (j.getNumber().equals(callRecord.getCallingNumber())) { ue = j.getUserRecords(); } else continue; if (callRecord.getAnswerAddressAreaCode().equals(callRecord.getCallingAddressAreaCode())) { // 判断地区 ue.addCallingInCityRecords(callRecord); } else if (callRecord.getAnswerAddressAreaCode().equals("0701") || Pattern.matches(regex00, callRecord.getAnswerAddressAreaCode())) { ue.addCallingInProvinceRecords(callRecord); } else ue.addCallingInLandRecords(callRecord); j.setUserRecords(ue); break; }
7-1 电信计费系列3-短信计费 各题目 设计与分析 踩坑心得 改进意见 核心代码分析:
(1)7—1 计算两点间的距离
设计与分析:
这题只需要用一个String的字符串去接收题目中的输入,再使用String中的方法spilt将字符串进行拆分
本题的难度在于判断输入的短信字符段,同意采用正则表达的,在计算字符串的长度就可以了。
踩坑心得:
实现这道题目主要就是截取到短信,在使用之前类似的方法就可以
改进建议:
1、由图可知,该题的圈复杂度为17。这里有点可惜,如果小于10代码质量就会很好。改进可以减少循环的使用。
2、这题可以将获取字符串变成一个字符数组。、此题可以将split方法改进,提高程序的执行效率
核心代码分析:
①正则表达式:
final String regex = "^u-[0-9]{11,12} 0$";// 座机开户u-[] 0;正则 final String regex0 = "^t-[0]{1}[0-9]{9,11} [0]{1}[0-9]{9,11} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; // 座机拨打电话格式正则 final String landtel = "t-[0]{1}[0-9]{9,11} 1[0-9]{10} [0-9]{3,4} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; // 座机--电话 final String teltel = "t-1[0-9]{10} [0-9]{3,4} 1[0-9]{10} [0-9]{3,4} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; final String telland = "t-1[0-9]{10} [0-9]{3,4} 0[0-9]{9,11} [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|(3[0-1])) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9]) [0-9]{4}[.](([1-9]{1})|([1]{1}[0-2]{1}))[.]([1-9]|([1-2]{1}[0-9]{1})|3[0-1]) (([0-1][0-9])|(2[0-3]))[:]([0-5][0-9])[:]([0-5][0-9])$"; final String regex00 = "^079[0-9]$";// 座机区号正则 final String regextel = "u-1[0-9]{10} 1$";// 手机开户正则 final String regexmessage= "u-1[0-9]{10} 3$"; final String regexmess = "m-1[0-9]{10} 1[0-9]{10} ([\\w]|[ ]|[.]|[,])+$";
②短信计费:
class SendMessageRule extends MessageChargeRule{ double calCost(ArrayList<MessageRecord> messageRecords) { int n=0; for(int i=0;i<messageRecords.size();i++) { if(messageRecords.get(i).getMessage().length()<=10) { n++; } else { if(messageRecords.get(i).getMessage().length()%10==0) n += messageRecords.get(i).getMessage().length()/10; else n += messageRecords.get(i).getMessage().length()/10+1; } } if(n<=3) return n*0.1; else if(n<=5) return 0.3+(n-3)*0.2; else return 0.7+(n-5)*0.3; } }实验四
设计与分析:
实验4(1)主要是理解看懂给出的类图,根据类图写出代码。理解各种抽象类之间的联系,最难的应该是GaneDate类型的理解。这个类把这几个物品之间联系起来,实现游戏的功能,这几个类图之间的代码实现
实验4(2)在原有的基础上把boat类继承到abstractObjectial即可。把abstractransport变成接口,再用boat实现这个接口,这道题就算写完。
在代码实现过程中遇见的最大问题就是看懂题给出的类图,写出类图这道题目就相当于完成了一大半,在这个基础上把各个类串在一起,理解每个类之间的关系,就可以完成这道题。为了避免修改农夫过河前几次代码中代码的主干,我在游戏类中保留了这些代码,这样的代码修改难度就降低了不少,最后运行完成后debug找出了一个逻辑上的错误,就提交了这次实验。
这是case31错误时的截图,这道题这个点我也是测试了非常久才给出了正确的答案。
原因在于一个函数
实验4(1)主要是理解看懂给出的类图,根据类图写出代码。理解各种抽象类之间的联系,最难的应该是GaneDate类型的理解。这个类把这几个物品之间联系起来,实现游戏的功能,这几个类图之间的代码实现
实验4(2)在原有的基础上把boat类继承到abstractObjectial即可。把abstractransport变成接口,再用boat实现这个接口,这道题就算写完。
在代码实现过程中遇见的最大问题就是看懂题给出的类图,写出类图这道题目就相当于完成了一大半,在这个基础上把各个类串在一起,理解每个类之间的关系,就可以完成这道题。为了避免修改农夫过河前几次代码中代码的主干,我在游戏类中保留了这些代码,这样的代码修改难度就降低了不少,最后运行完成后debug找出了一个逻辑上的错误,就提交了这次实验。
改进意见:
1、如复杂度分析图所示,本题的圈复杂度为33,超过了代码的一般范围,这就意味这该代码的可读性非常的差,而对与前文提到的方法都可有效减少圈复杂度。
2、本题的代码有两百多行,代码的简化就显得尤为重要,应该适当的合适代码的逻辑,让代码模块化。
3、如果能够将代码的逻辑颠倒一下,程序将更加的合理。
核心代码如下:
public abstract class AbstracTransport { private String place ="a";public String departure ="a"; private int capacity; private ArrayList<MaterialObject>goodses = new ArrayList<MaterialObject>(); abstract public void moveTo( String destination); public int getCapacity() { return capacity; } public void setCapacity(int capacity) { this.capacity = capacity; } public String getPlace() { return place; } public void setPlace(String place) { this.place = place; } public ArrayList<MaterialObject> getGoodses() { return goodses; } public void setGoodses(ArrayList<MaterialObject> goodses) { this.goodses = goodses; } } class Boat extends AbstracTransport{ public void moveTo(String destination) { if(this.getPlace() == destination) setPlace(departure); else setPlace(destination); } public void crossRiver(Person person) { if(person.isPlace()) person.setPlace(departure); else person.setPlace("b"); } public void crossRiver(Person person, MaterialObject m) { this.crossRiver(person); if(m.isPlace()) m.setPlace(departure); else m.setPlace("b"); } public void broad(MaterialObject m) { getGoodses().add(m); } public void disembark(MaterialObject m){ getGoodses().remove(m); } } public abstract class AbstractGame { private AbstractRule gameOverRule = new GameOverRule(); private AbstractRule gameSuccessRule = new GameSuccessRule(); private GameData gameDate = new GameData(); public abstract void play() ; } public abstract class AbstractRule { public abstract boolean judge(GameData gameData); } class CrossRiverRule extends AbstractRule{ public boolean judge(GameData gameData) { if(hasCross(gameData.cabbage)&&hasCross(gameData.sheep)&&hasCross(gameData.wolf)&&hasCross(gameData.farmer)) return true; return false; } public boolean hasCross(MaterialObject m) { if( m.getPlace().equals(m.destination)) return true; return false; } } class ObjectExistRule extends AbstractRule{ public boolean judge(GameData gameData){ if(gameData.sheep.isExist()&&gameData.wolf.isExist()&&gameData.cabbage.isExist()) return true; return false; } } class GameOverRule extends AbstractRule{ ObjectExistRule oe= new ObjectExistRule(); CrossRiverRule cr = new CrossRiverRule(); public boolean judge(GameData gameData) { if(!oe.judge(gameData)) return true; if(cr.judge(gameData)) return true; return false; } }学习心得: 1、通过三次作业的练习,强化了我对java语言的使用和理解,学习了很多编程时使用的技巧和方法,深化了我书写代码时的逻辑理解和对算法的实现能力,拔高了我对程序设计的眼界和深度,领悟自上而下逐步细化的编程思想更加的透彻 2、知道了写程序时细节决定成败,必须一丝不苟,让我对自己的水平有了一定的认知,提醒我今后的学习还有很多改进的地方,深刻了解了关于Java程序结构和代码书写的规范,以及要遵守的代码书写规范 3、积累了一些debug的经验,深刻认识到了调试的重要性,学会了调试的基本技巧,如何设置断点,单步进入,跟踪参数,以及更改代码的逻辑顺序,排除逻辑错误,对提高代码的质量大有改善。 4、学会了正则表达式的运用,合理的运用正则表达式能有效减少判断格式代码的行数,还可以准确的判断出输入的正确性。 5、面对复杂的类图时不要盲目的下手,要寻找到一个突破口,从一点上去逐步书写整个代码,这样写出来的代码就会条理比较清晰,效果也更好。