第一次Blog作业
一、前言
前三次作业的题目虽然题量一次比一次少,但题目的难度越来越难,所花的时间也越来越多。由于上学期学的c语言,这学期学Java,感觉两者有许多相同之处,也有许多不同之处,因此在用java编写程序时,代码就像新手一样写的不是很简洁。这三次作业都是自己花了很多时间做的,从一个不懂的小白到初步学到了一丁点皮毛。话不多说,下面对三次作业逐次分析其所用的知识点。
(1)第一次作业
1.计算两个数的和
算是入门级的题目了,首先写个import java.util.Scanner; 表示把util包下的Scanner类导入程序中,然后通过new Scanner(Sysem.in)创建一个Scanner对象,这样就能从键盘输入字符或者数字了,用nextInt()方法定义整形数,类似学习到nextFloat()、nextDouble()处理浮点型,next()、nextLine()定义字符型,最后用System.out.println进行输出,其中println和print的区别一个为换行输出,一个为不换行输出。
2.电话键盘字母数字转换
此题就涉及到字符与数字之间的关系了,通过借鉴别人的经验,学习到定义单个字符可以用next().charAt(0);利用if-else嵌套判断语句对字符和数字之间的关系进行转化。
3.成绩分级管理
与上面的2类似,同样考察的是判断语句的使用。
4.计算税率
与上面的2、3类似,考察的是判断语句的使用。
5.计算钱币
考察的是算数运算符+-*/%的使用,其中%表示求余数。
6.使用一维数组求平均值
此题考察的是数组的运用,定义一个整形数组int[] array = new int[],后面[]中表示数组中元素的个数。
7.对多个整数进行排序
考察数组的创建、排序算法的使用,结合上学期c语言的经验,可以用冒泡排序,选择排序等对数字大小进行排序。
8.判断三角形类型
考察判断语句的使用。
(2)第二次作业
1.IP地址转换
考察的是二进制字符与十进制数的转化,这里我用了substring()分割字符串得到字符串,然后再对子字符串进行处理转化为十进制数。
2.合并两个有序数组为新的有序数组
考察的是数组的相关知识,合并可以用第三个数组表示,再对第三个数组进行排序处理。
3.判断闰年及星期几
考察的是闰年的判定算法,这在c语言中也经常出现,主要利用if语句判断数据的合法性,判断闰年和平年等。
4.求下一天
与3类似,主要考察算法的应用。
5.求前N天
与3、4类似,考察算法。
(3)第三次作业
1.创建账户类Account
考察类与对象的关系,对类的属性进行调用,对类中的额方法进行调用,这里涉及到了时间的创建,用了LocalDate类型,同时保留两位小数可以用String.format()方法。
2.定义日期类
与1类似,考察了类的创建,对类进行处理,这里还借用了第二次作业中的4的算法,将求下一天设置为类的一个方法,随后根据输入数据的合法性进行相应的输出。
3.一元多项式求导
这道题是这三次作业中最难的,主要考察的是字符串的处理(split()方法分割字符串,replaceAll()、replaceFirst()方法替换字符串中的字符,StringBuffer()方法对多个字符串进行连接处理,valueOf()进行数字到字符串的转化),用到了正则表达式对数据的判断(matches()匹配字符串,Pattern和Mattern类中group()方法对字符串分批处理),用到了大数BigInteger()方法,用到了存数据的linkedList()方法。
二、设计与分析
主要对第一次作业中的8和第二次作业中的4、5和第三次作业中的2、3进行设计分析。
1.判断三角形类型
我的代码是直接使用if-else直接判定三角形三边的关系并输出相应的结果,对应分析报告如下:



不难看出,最高复杂度很高达到了26,属于垃圾代码了。于是多新建了个方法用于判断数据的合法性,发现最高复杂度降成了21,依然是垃圾代码。暂时还没想到更高级的方法让复杂度降的更低。
2.求下一天
由于这道题也是用了挺多的if-else语句,使得提交的源码最高复杂度到了34,如下图:
判断年份是否为闰年代码:
public static boolean isLeapYear(int year) { if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) return true; else return false; }
判断数据合法性代码:
public static boolean checkInputValidity(int year,int month,int day) { if (year >= 1820 && year <= 2020 && month >= 1 && month <= 12 && day >= 1 && day <= 31) { if (month == 2) { if (isLeapYear(year) == true) { if (day <= 29 && day >= 1) return true; else return false; } if (isLeapYear(year) == false) { if (day <= 28 && day >= 1) return true; else return false; } } else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { if (day <= 31 && day >= 1) return true; else return false; } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day <= 30 && day >= 1) return true; else return false; } else return false; } return false; }
发现 else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) 这段是没意义的,数据肯定是正常的,可以删去。
同时 day >= 1在每个if语句中也是多余的,因为大前提就是day >= 1。这段代码复杂度为34。
改进 :
public static boolean checkInputValidity(int year,int month,int day) { if (year >= 1820 && year <= 2020 && month >= 1 && month <= 12 && day >= 1 && day <= 31) { if (month == 2) { if (isLeapYear(year) == true) { if (day <= 29) return true; } if (isLeapYear(year) == false) { if (day <= 28) return true; } } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day <= 30) return true; } else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { return true; } else { return false; } } return false; }
发现复杂度降低到了25!
求下一天代码:
public static void nextDate(int year,int month,int day) { day = day + 1; if (month == 2) { if (isLeapYear(year) == true) { if (day > 29) { month = month + 1; day = day - 29; } } if (isLeapYear(year) == false) { if (day > 28) { month = month + 1; day = day - 28; } } } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day > 30) { month = month + 1; day = day - 30; } } else { if (day > 31) { month = month + 1; day = day - 31; } } if (month > 12) { month = month - 12; year = year + 1; } System.out.println("Next date is:" + year + "-" + month + "-" + day); }
尽管复杂度较高,但依现在的经验来看,个人还是觉得用if-else语句比较直观的判断数据,比较适合初学if-else语句的人,通俗易懂。
3.求前N天
由于该题与 2.求下一天 几近相似,都是用了大量的if-else语句对数据进行处理,就是其中某一个算法不一样,所以这题跟上一题的复杂度都很高。
4.定义日期类
先看分析报告:

报告显示,复杂度最高到了25,这是由于算法检验数据的合法性用了 2.求下一天 中的某个类似算法(使用了大量if-else检验数据),导致这部分模块的复杂度也很高,求下一天也是利用 2.求下一天 中的求下一天方法进行数据处理,复杂度也较高。
再看类图:

类图就比较清晰的显示了整个结构框架。
5.一元多项式求导

因为这道题花了很多时间去构思算法和学习新方法,没时间去构思类的设计,所以类就只设定了一个检验函数的类,比较潦草。
用正则表达式检验表达式合法性的代码:
return function.matches("(([+]*[-]*[x])|([+]*[-]*[x][\\^][+]*[-]*[1-9][0-9]*)|([+]*[-]*[1-9][0-9]*[*][x])|([+]*[-]*[1-9][0-9]*[*][x][\\^][+]*[-]*[1-9][0-9]*)|([+]*[-]*[0])|([+]*[-]*[1-9][0-9]*))*");
匹配函数表达式每项的变量项和常数项的正则表达式:[-]*[+]*([1-9]+[0-9]*)*[*]*[x]([\\^][-]*[+]*)*([1-9]+[0-9]*)*|[-]*[+]*[0-9]*。
分析报告:

其中求导算法的复杂度较高,用了大量的if-else匹配各种情况,例如指数为1,x^不输出,系数为1,1*不输出等情况。
小分析:
经过上面复杂度的测试可以看出,if-else语句容易使复杂度变大,同时类与方法的不合理设计也会增加程序的复杂度。类图可以清楚的显示各个部分框架的信息,比较适合写代码前的设计,因此,以后写代码可以先做个类图,然后再对类中的各个部分进行具体操作。
三、踩坑心得
1.判断三角形类型
该题存在一个坑,题目要求的是实型数,那么如果用float型的话,当输入其中两个数的小数位超过8位时,比如3.111111111(9个1)和3.1111111111(10个1)和 3.11111111111(11个1),那么输出结果会是等边三角形,而不是普通三角形(实际情况下的结果),用double型会出现同样的问题。

2.判断闰年及星期几
这里有个public static int numOfDays(int year,int month ,int day) ;返回天数的方法。在求月份日期的天数时,起初我想直接进行闰年判断,然后在对数据进行加减乘除,如设置1月共31天,2月共31+29天......闰年7月19,那么这一年的天数为前6个月的天数加上7月的19天,这样就显得很没有逻辑性,只有答案,没有过程,后来用了数组存每个月的天数,然后再用循环对天数进行输出,就显得比较有条理性了。
部分代码展示:
int[] array1 = {31,29,31,30,31,30,31,31,30,31,30}; int[] array2 = {31,28,31,30,31,30,31,31,30,31,30}; for (i = 0;i < (month-1);i ++) { //计算月份天数 if (isLeapYear(year) == true) sum = sum + array1[i]; else sum = sum + array2[i]; } sum = sum + day;
3.一元多项式求导
在阅读完指导书后,意外发现有个很大的数(20位),超过了double类型的范围,于是网上查资料,偶然发现BigInteger()类型的可以存无数位的数,但是在用BigInteger定义的变量时,用了 +-*/等运算符直接对变量进运算时,报错:
原来BigInteger里面是不能直接用运算符+之类的进行运算的,而是要用add之类的进行运算。
此外,这里题目的测试点10是个坑,变量项求导后得常数项并且是个正数的话,那么这个正数就不能带+号,由下图:

按照实际情况的话,输出结果应该是9*x^2-2+4-198*x,而不是9*x^2-24-198*x,但是能过测试点10的就是后一个答案9*x^2-24-198*x。
四、改进建议
1.判断三角形类型
该题可以进行优化,比如选定更高精度的double型数据,或者设定好输入数据的范围,让这个代码更加符合java的规则同时也符合现实情况
2.IP地址转化
在这里,原提交的代码中,我用了一个for语句,对字符串中的每个字符先进行判断然后进行转化处理,显得比较呆,后来发现有个更简单的方法Integer.parseInt()可以直接将字符串进行转化,结合现在对正则表达式的使用,可以使代码更简洁。
完整代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String str = input.nextLine();
if (str.matches("[0-1]{32}") == true) {
String str1 = str.substring(0,8);
String str2 = str.substring(8,16);
String str3 = str.substring(16,24);
String str4 = str.substring(24,32);
int num1 = Integer.parseInt(str1,2);
int num2 = Integer.parseInt(str2,2);
int num3 = Integer.parseInt(str3,2);
int num4 = Integer.parseInt(str4,2);
System.out.println(num1+"."+num2+"."+num3+"."+num4);
}
else {
System.out.println("Wrong Format");
}
}
}
最大复杂度瞬间从原垃圾代码的21降到了现在的3!
3.一元多项式求导
要想改进编码的话,那就是把函数独做一个类,然后求导是个算法,检验函数合法性也是个算法。把常数项和变量项又单独成一个类,其属性分别为系数、指数和常数,这样整个编码就显得比较有框架,条理性更强。
五、总结
经过这三次作业,学到了很多东西(数据类型的定义,类的创建及属性和类中方法的调用,各种方法的使用),关于类的创建,如果用private去修饰一个变量,那么可以用setter方法去设置该变量的值,然后在用getter方法去调用该变量的值。其中求导那道题比较的让我有大的收获,因为这道题学到了很多新东西,关于字符串处理的String.matches()、replaceAll()、replaceFirst()、split()方法,正则表达式中的Pattern()、Mattern()类中的compile()、matcher()、find()、group()方法让我对正则表达式有所进一步了解。关于链表linkedList()的使用,增加成员用add(),获取某个成员用get()。还有大数BigInteger()的使用,不能简单用算术运算符+-*/对数据进行相应的运算,如果是加要用add(),减用subtract(),乘用multiply(),除用divide(),两个大数相比要用compareTo()。
关于学习,在类的方面还不够了解,比如这道导数题,想不到应该怎么创建各种类(变量项的类,常量项的类,表达式的类等)来搭配各自间的关系,因此,在类的方面,还需要加大练习,同时也要加大各种框架的构建。关于作业、实验,个人觉得还行,渐循渐进,利用知识的消化吸收。课程部分采用线上线下组合的形式,同时设置几道线上互测,通过认真阅读别人的代码能给自己带来启发,提高自己的算法能力。

浙公网安备 33010602011771号