面向对象第一单元总结
2019-03-24
作业要求:求导表达式,由x,sin(x),cos(x)和嵌套类型组成的多项式。
-5*x^+6, sin(x)^-7*-5*x^9*-2*cos(x), sin((3*x^2+cos((x))^3))+...
一.基于度量来分析自己的程序结构
三次作业的结构都有较大的变化,从图片可以看出,由于对面向对象知识了解有限,所以我周周都在重构代码。回顾这三次作业,我对面向对象的了解仍然比较表面,但可以清晰地看到我进步的曲线。
1. 第一次作业
整体的复杂度不算很高。
思路:按照加减法把项分为标准a*x^b的形式,把系数和指数存进数据结构,再进行化简。
缺点:
1. 非常面向过程。只有两个类,一个ComputePoly,用来写实现功能的方法;一个Poly,只是为了存取属性。
2. 格式(Wrong Format)处理的非常混乱。
3. 对于数据结构不太熟悉,出现了一部分代码复用较低的情况。
优点:逐步熟悉Java,用了较好的数据结构来处理数据。
1.使用<TreeMap>(用<HashMap>也一样)来存放项,通过x的指数来合并同类项。
2.给合适的地方插空格,使用spilt()方法分项。
3.README写得很好,也是唯一一次认真写了README。
2. 第二次作业
复杂度集中在checkBeforeDelBlanks()和Keys.compareTo()上,前者是因为调用了很多次Matcher和Pattern进行判断。这一块在后面考虑到采取其他方法check。后者是因为重写了compareTo方法,有几个指数需要判断大小,所以使得复杂度增加。优化的方法可以考虑用别的表示形式,不一定非要给map的Key重写一个类。
思路:
建立Term类标准化项A*x^a*sinx^b*cosx^c;
用TreeMap<BigInteger,Term>存放项
用TreeMap<Trifunc,Term>把x^a作为Key,重写CompareTo方法进行化简的准备
将两种TreeMap转换后,进行化简
把结果转化为ArrayList进行toString化简形式后输出
缺点:
1. 和第一次作业的作业流程相似,所以仍然是面向过程的思路。
2. 对数据属性的存取不太理解,所以数据管理得相当混乱,一个类只能使用一次。
优点:
1.使用IOException来处理所有错误输出的情况,并在第二行报告错误原因。
2.较第一次作业,建了更多的类来处理函数而不是用方法来处理对象。
3.使用泛型设计数据结构,使其更好进行化简操作,当然也变得不好理解。
4.耗费了大量的精力来进行多项式化简,虽然并没有体验出什么从设计上的创新,主要是从数学分类的角度进行化简, 但还是取得了不错的化简效果。
化简主要分为两类:
第一类是x^c*(A*sinx^a+B*cosx^b)的提取公共项降次化简(不过由于水平原因不会处理次数是负数的情况)
第二类是1-sinx^2=cosx^2 / 1-cosx^2=sinx^2 的化简,由于之前的代码已经很复杂了, 所以没有在这一步的基础上,提取公共项。
特别注意的是化简过程中有可能来回变化,所以一定要注意化简的先后顺序。
3. 第三次作业
除了第二次作业也遇到的checkBeforeDelBlanks(),其他的复杂度主要出现在一个问题上——括号。对于表达式类型(包括加法和减法)和乘法类型用栈的方式来检查是括号内还是括号外,对于Exp还要特别判断是表达式因子还是表达式。而判断表达式因子又需要双重判断。在我写代码时就发现这一块的复杂度特别高,我也尽量时代码复用来缩减代码编写的长度,不过效果也不理想。现在来考虑的话,或许会把这两部分提成一个新的类来判定,并且或许会用传递属性/参数的情况来减少重复判断的次数。
思路:
最后一次作业用了很长时间理解,但实现起来非常快。由于放弃化简的缘故,也不容易出现计算方面的错误。
1.设计接口类Derviation。
2.设计工厂模式用于返回不同类型的对象实现接口类的多态求导。
3.所有操作通过字符串的形式,在诸如出现-号或表达式因子()的情况额外加一层括号。
缺点:
1.用字符串操作,采取递归的策略,无法边求导边化简。只有化简最后的多项式,实现起来太复杂。
2.对于表达式因子的操作有些复杂,没有很好的代码复用。
优点:
1.较于前两次,挺面向对象了。
2.工厂模式中既能够判断类型,也能够返回不同类型的新对象用于多态,使代码很美观。
3.简单清晰,计算不容易出错。
二.分析自己程序的bug
三次强测中没有bug,但在前两次互测时有bug,最后一次不能提交WF所以侥幸没有bug。每一次的bug都是WrongFormat的问题。出现WF的bug根本原因是我采取的表达式检查的方式特别弱,并且自己对于WF会出现的情况思考得太少太片面。
我的InputCheck的思路是:
1.检查非法字符
2.检查空格导致的错误
3.去掉空格
4.合并符号
5.通过split()或者find()的方法单独检查每一项
问题就在2.3.4步会产生新的问题,使本来有错误的输入变成正确的。并且随着问题变得更加复杂,会使特判的种类大大增加。在互测的环节,我通过同学的代码了解到把空格和符号加入进正则表达式的判断会大大减小错误的可能性,但着同样也会使处理字符串变得更加复杂。
三.分析自己发现别人程序bug所采用的策略
1.白盒检查,主要用于还不太复杂的第一次作业
①直接检查正则表达式的错误
②用debug模式,查看有无其他错误。特别是增设的方法导致的新错误。
2.黑盒检查,用git bash、shell脚本、python、octave和Notepad++、VScode编辑器共同实现。
①编写python的代码
- 用octave比较数据的版本
from sympy import * x = symbols("x") y = input() dify = diff(y,x) print(dify)
- 用python比较数据的版本(来源于莫策同学)
from sympy import * from sympy.abc import x if __name__ == "__main__": formula = str(input()) if (len(formula) != 0): formula = formula.replace("^", "**") dfx = diff (formula); val = dfx.subs('x', 2.6) tmp = str(dfx) tmp = tmp.replace("**", "^") print("diff: ",tmp) print("value: ", val)
②把各个程序打包成jar形式
- 方法很简单,只需特别注意同学输出方式是否换行,没换行的手动换行以后再打包。
③编写shell脚本
cat $testfile | while read line do echo "$arc" >> $compare echo "$line" | java -jar $arc >> $compare echo "$line" | java -jar $arc | python test.py >> $compare echo "-----------------------------------------">>$compare echo "$ass" >> $compare echo "$line" | java -jar $ass >> $compare echo "$line" | java -jar $ass | python test.py >> $compare echo "-----------------------------------------">>$compare #省略若干个 echo " " >> $compare done
- 同样也可以生成octave/matlab合适的矩阵形式。
④在git bash运行
3. 总的来说这是一种半自动化的形式,需要人工肉眼去比对以及确定是哪一个测试点。但是比较好的是这样的对拍实现起来特别容易,不熟练的情况也可以半个小时内写完。
4.对于构建测试样例,我采用了C语言生成或者自己构造的方式,不过效果不是很好。会思考其他的思路来构建测试集。
四. Applying Creational Pattern
在第三次作业时我应用了工厂模式,这也是我认为最适合的方式。当然也可以在这个基础上同时应用其他的设计模式,使得程序更好。
以下主要说明我在第三次作业使用工厂模式的方式:
目的:实现接口的多态
s = result + toDerivate(getFactor(TypeStr(tmp), tmp));
这条语句是我在每一个接口里使用的求导方法,也是我实现递归求导的方式。在使用工厂模式时,我并不知道有<设计模式>这个东西,但我非常需要有那么一个类可以返回不同的类型的对象(因为类的返回类型是需要确定的)。经过我一天的到处寻找解决策略,最终选择了工厂模式来达到这个目的。我的工厂模式有两个最核心的方法。
- getFactor()
public static Derivation getFactor(String factorType, String str) throws IOException { if (factorType.equals("CONSTANT")) { return new Constant(str); } if (factorType.equals("POWER")) { return new Power(str); } if (factorType.equals("COS")) { return new Cos(str); } //...省略其他的类型 if (factorType.equals("MULTIPLY")) { return new Multiply(str); } throw new IOException("noFactorType"); }
- TypeStr()
public static String TypeStr(String str) throws IOException { if (str.matches("[+-]?\d+")) { return "CONSTANT"; } if (str.matches("x(\^[+-]?\d+)?")) { return "POWER"; } if (str.matches("cos\(x\)(\^[+-]?\d+)?")) { return "COS"; } //...省略其他的类型 if (isCosCompond(str)) { return "COSCOMPOND"; } if (isSinCompond(str)) { return "SINCOMPOND"; } throw new IOException("wrongFactor"); }
第二个方法用正则表达式和栈的方式来判定输入的表达式类型,并把结果返回成字符串的标识(当然也可以用宏)。第一个方法是根据类型的标志来创建新的对象并返回。
以上就是应用设计模式的全部内容,虽然自己对设计模式的精髓并没有理解,并且这里面还有更多更多的内容需要学习,不过这也不是急于求成的事情。希望自己可以多多学习,变得更厉害一些叭!
总结的总结
第一个单元完成了,我其实只在最后一个作业稍微面向对象了。相信在后来的作业会更进一步地理解面向对象的思想。
希望就算没有对象也能好好面向对象(╹ڡ╹ )
//谢谢你看到了这里 (‾◡◝)