BUAA_OO 第一单元总结
BUAA_OO 第一单元总结
第一次作业
需要完成的任务为简单多项式导函数的求解,多项式只有幂函数和系数组成。
- 规模度量
| Source File | Total Lines | Source Code Lines |
|---|---|---|
| Derivation.java | 134 | 119 |
- 复杂度度量
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| Derivation.Term.Term(String) | 25 | 1 | 11 | 11 |
| Derivation.Term.getDeg() | 0 | 1 | 1 | 1 |
| Derivation.Term.getRatio() | 0 | 1 | 1 | 1 |
| Derivation.Term.setDeg(BigInteger) | 0 | 1 | 1 | 1 |
| Derivation.Term.setRatio(BigInteger) | 0 | 1 | 1 | 1 |
| Derivation.main(String[]) | 13 | 5 | 7 | 9 |
由上述类复杂度分析可以看出,Derivation.Term这个类的复杂度较高,Term的构造方法由于需要处理表达式项的符号、系数、指数,因此逻辑较多,功能较为复杂。
- 类图

-
Derivation类是入口。Term类用于解析一个项,并计算系数和指数。
-
思路:先用正则提取出每一个项来,对于每一个项,再用正则匹配出每一个因子来。每个项内所有的系数相乘,指数相加。
-
在互测中通过Python写了一个自动评测机,将生成多项式 \(f\) 写入到一个input.txt中,运行java程序,重定向从input.txt读入数据,输出到ouput.txt中。从output.txt中读取 \(g\),比较(f.diff()-g).simplif() 是否为 0。
-
在强测和互测中未发现Bug。
在互测中通过自动评测机的方式找到了一位同学的Bug。评测机数据按照递归的方式随机生成。
-
性能:只进行了把正系数项往前放这一个优化, 甚至x ** 0,-1 * x 这种都没有优化,导致性能分比较低。x**2可以优化为 x*x 没有想到。
-
反思:正则表达式太长,当输入只有一个项,且长度>2000时会爆栈。在开发的过程中应该避免较长的正则表达式。
第二次作业
完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解
- 规模度量
| Source File | Total Lines | Source Code Lines |
|---|---|---|
| Add.java | 42 | 32 |
| Constant.java | 27 | 21 |
| Cos.java | 27 | 22 |
| Diff.java | 134 | 124 |
| Mul.java | 74 | 66 |
| Node.java | 73 | 65 |
| Operator.java | 8 | 5 |
| Pow.java | 42 | 36 |
| Sin.java | 25 | 20 |
| X.java | 18 | 14 |
- 复杂性分析
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| Add.Add(Operator,Operator) | 0 | 1 | 1 | 1 |
| Add.addT(Operator,Operator) | 6 | 4 | 4 | 7 |
| Add.diff() | 0 | 1 | 1 | 1 |
| Add.simplify() | 0 | 1 | 1 | 1 |
| Add.toString() | 0 | 1 | 1 | 1 |
| Constant.Constant(BigInteger) | 0 | 1 | 1 | 1 |
| Constant.diff() | 0 | 1 | 1 | 1 |
| Constant.getCst() | 0 | 1 | 1 | 1 |
| Constant.simplify() | 0 | 1 | 1 | 1 |
| Constant.toString() | 0 | 1 | 1 | 1 |
| Cos.Cos(Operator) | 0 | 1 | 1 | 1 |
| Cos.diff() | 0 | 1 | 1 | 1 |
| Cos.simplify() | 0 | 1 | 1 | 1 |
| Cos.toString() | 0 | 1 | 1 | 1 |
| Diff.main(String[]) | 1 | 1 | 2 | 2 |
| Diff.nextBraket(String,int) | 11 | 3 | 5 | 7 |
| Diff.parse(String) | 35 | 1 | 16 | 16 |
| Diff.step1(String,Matcher,ArrayList |
9 | 1 | 6 | 6 |
| Mul.Mul(Operator,Operator) | 0 | 1 | 1 | 1 |
| Mul.diff() | 0 | 1 | 1 | 1 |
| Mul.mulT(Operator,Operator) | 10 | 6 | 8 | 11 |
| Mul.simplify() | 5 | 1 | 4 | 4 |
| Mul.toString() | 4 | 1 | 3 | 3 |
| Node.Node(BigInteger,BigInteger,BigInteger,BigInteger,Operator) | 0 | 1 | 1 | 1 |
| Node.build() | 16 | 1 | 9 | 9 |
| Node.getcst() | 0 | 1 | 1 | 1 |
| Node.getpcos() | 0 | 1 | 1 | 1 |
| Node.getpsin() | 0 | 1 | 1 | 1 |
| Node.getpx() | 0 | 1 | 1 | 1 |
| Node.getroot() | 0 | 1 | 1 | 1 |
| Pow.Pow(Operator,BigInteger) | 0 | 1 | 1 | 1 |
| Pow.diff() | 0 | 1 | 1 | 1 |
| Pow.powT(Operator,BigInteger) | 2 | 3 | 2 | 3 |
| Pow.simplify() | 0 | 1 | 1 | 1 |
| Pow.toString() | 0 | 1 | 1 | 1 |
| Sin.Sin(Operator) | 0 | 1 | 1 | 1 |
| Sin.diff() | 0 | 1 | 1 | 1 |
| Sin.simplify() | 0 | 1 | 1 | 1 |
| Sin.toString() | 0 | 1 | 1 | 1 |
| X.diff() | 0 | 1 | 1 | 1 |
| X.simplify() | 0 | 1 | 1 | 1 |
| X.toString() | 0 | 1 | 1 | 1 |
由上述类复杂度分析可以看出,Diff.parse这个类的复杂度较高,这个类是用来解析字符串的。
- 类图

-
重构:因为第一次作业我的代码没有任何可扩展性,所以本次作业进行了代码重构,按照面向对象的方式重构了代码。使用工厂模式、接口以及多态。
定义了接口Operator,所有的运算,幂(Pow),加法(Pow),乘法(Mul),Sin,Cos,常量(Constant),变量(X)都继承自Operator。Operator中有三个方法,toString,求导diff(),和simplify()。
首先由Diff.parse将字符串解析为表达式树。解析时如果遇到左括号,就寻找与之匹配的右括号,然后递归处理中间的部分,并返回一棵表达式树。
每个节点视节点类型拥有0,1,2个子节点,比如Mul类有两个子节点,Sin类有1个子节点。
将运算符和因子看作可求导对象,建立表达式树。然后对表达式树求导,化简。
-
对于求导,每个Operator都有一个diff方法,比如Mul类的两个子节点为factor1,factor2,那么调用diff,按照乘法的求导法则,factor1*factor2.diff()+factor1.diff()*factor2新形成表达式树。
-
化简(simplify)采用的策略是,同一个项内能乘在一起的乘在一起,返回一个Node类,负责存放化简得结果,记作一个五元组(常数,x指数,sin指数,cos指数,不会化简的部分)。主要的优化有
0*() = 01*() = 10 + () = ()()**1 = () -
在强测和互测中未发现Bug。
-
自动评测机:在数据生成时记录嵌套深度,如果嵌套深度达到设定的阈值就不再继续往下嵌套生成。
-
用自动评测机找到了一位同学的Bug。评测机数据按照递归的方式随机生成,所以强度不高。也没有构造一些极限数据,例如\(x*(x*(x+1)+1)\)和\((x+1)*(x+1)*(x+1)\)之类的数据去hack。有些人的代码会在处理25个\((x+1)\)连乘的时候会TLE,猜测是先求导,然后括号展开,然后化简。解决方法应该先括号展开,再求导,再括号展开,再化简就可以避免TLE。
-
性能:为了避免化简产生的错误,没有做过多的化简,连-1*x这种都没有化简,性能分略低。而且我的代码有的时候会输出 \(x+-1\) 这种情况,导致有的测试点自己的长度比最短长度长很多。
第三次作业
完成的任务为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解。
由于第二次作业的架构,所以第三次作业改动比较少。主要是在解析字符串的时候判断WrongFormat有不少改动。
- 规模度量
| Source File | Total Lines | Source Code Lines |
|---|---|---|
| Add.java | 48 | 38 |
| Constant.java | 27 | 21 |
| Cos.java | 37 | 32 |
| Diff.java | 212 | 186 |
| Mul.java | 80 | 72 |
| Node.java | 73 | 65 |
| Operator.java | 8 | 5 |
| Pow.java | 43 | 37 |
| Sin.java | 35 | 30 |
| X.java | 18 | 14 |
- 复杂性分析
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| Add.Add(Operator,Operator) | 0 | 1 | 1 | 1 |
| Add.addT(Operator,Operator) | 8 | 6 | 4 | 9 |
| Add.diff() | 0 | 1 | 1 | 1 |
| Add.simplify() | 0 | 1 | 1 | 1 |
| Add.toString() | 0 | 1 | 1 | 1 |
| Constant.Constant(BigInteger) | 0 | 1 | 1 | 1 |
| Constant.diff() | 0 | 1 | 1 | 1 |
| Constant.getCst() | 0 | 1 | 1 | 1 |
| Constant.simplify() | 0 | 1 | 1 | 1 |
| Constant.toString() | 0 | 1 | 1 | 1 |
| Cos.Cos(Operator) | 0 | 1 | 1 | 1 |
| Cos.diff() | 0 | 1 | 1 | 1 |
| Cos.simplify() | 2 | 2 | 2 | 2 |
| Cos.toString() | 3 | 2 | 1 | 6 |
| Diff.checkPow(int,String,String) | 3 | 4 | 1 | 4 |
| Diff.checkTerm(String) | 9 | 2 | 3 | 8 |
| Diff.main(String[]) | 2 | 1 | 3 | 3 |
| Diff.nextBraket(String,int) | 11 | 6 | 3 | 7 |
| Diff.parse(String) | 34 | 2 | 17 | 20 |
| Diff.step1(String,ArrayList |
15 | 6 | 9 | 11 |
| Mul.Mul(Operator,Operator) | 0 | 1 | 1 | 1 |
| Mul.diff() | 0 | 1 | 1 | 1 |
| Mul.mulT(Operator,Operator) | 12 | 8 | 8 | 13 |
| Mul.simplify() | 5 | 1 | 4 | 4 |
| Mul.toString() | 4 | 1 | 3 | 3 |
| Node.Node(BigInteger,BigInteger,BigInteger,BigInteger,Operator) | 0 | 1 | 1 | 1 |
| Node.build() | 16 | 1 | 9 | 9 |
| Node.getcst() | 0 | 1 | 1 | 1 |
| Node.getpcos() | 0 | 1 | 1 | 1 |
| Node.getpsin() | 0 | 1 | 1 | 1 |
| Node.getpx() | 0 | 1 | 1 | 1 |
| Node.getroot() | 0 | 1 | 1 | 1 |
| Pow.Pow(Operator,BigInteger) | 0 | 1 | 1 | 1 |
| Pow.diff() | 0 | 1 | 1 | 1 |
| Pow.powT(Operator,BigInteger) | 2 | 3 | 2 | 3 |
| Pow.simplify() | 1 | 1 | 2 | 2 |
| Pow.toString() | 0 | 1 | 1 | 1 |
| Sin.Sin(Operator) | 0 | 1 | 1 | 1 |
| Sin.diff() | 0 | 1 | 1 | 1 |
| Sin.simplify() | 2 | 2 | 2 | 2 |
| Sin.toString() | 3 | 2 | 1 | 6 |
| X.diff() | 0 | 1 | 1 | 1 |
| X.simplify() | 0 | 1 | 1 | 1 |
| X.toString() | 0 | 1 | 1 | 1 |
Diff.parse复杂度过高,最终导致出现了Bug。
parse时,如果匹配到sin(,就去寻找与之对应的右括号,然后递归处理括号内的内容。
- 类图

-
判断WrongFormat的思路是,在解析的过程中如果遇到格式错误的token,就抛出Exception()异常。如果捕获到异常就直接输出
Wrong Format! -
在强测中错了一个点,因为没有判断出WrongFormat。原因在于x**2**3这种没有判断出来。通过复杂性分析可以看出,恰好是在复杂度最高的方法里面出了问题。parse方法if判断太多,过于复杂,导致最终漏掉了这种情况。
-
优化策略沿用第二次作业。
在互测过程中没有被发现Bug。
在互测中通过自动评测机的方式找到了一位同学的Bug。评测机数据按照递归的方式随机生成。评测机数据按照递归的方式随机生成,所以强度不高。也没有构造一些极限数据,例如\(sin(sin(sin(x)))\)之类的数据去hack,而且互测输入长度限制太短,无法做到卡TLE这种hack。
-
自动评测机:因为带有sin()的嵌套导致Sympy在simplify()时用时过多,所以改为在[-10,10]上随机取几个点,带入到表达式中,对比两个表达式的相对误差,相对误差1e-3以内就算通过。
-
反思:复杂度高的类确实出现Bug,导致有一种情况没有考虑到。在测试的过程中确实没有过多得测试WrongFormat类型。
没有尝试递归下降的做法,或许尝试了可能更简单并且不至于写得太复杂而出错233333。
心得体会 && 反思:
-
经过面向对象第一单元的三次作业和博客总结,理解了面向对象和面向过程的区别,认识到面向对象在构建复杂项目时的重要性。
-
经过三次的迭代开发,我感受到程序架构的重要性,写一个可以支持扩展的架构在后期的迭代开发中至关重要。虽然思考一个好的架构会花费很多时间,但好的架构在爹带开发中可以节省很多时间。
-
好的架构离不开好的对象设计,对象的设计应该解耦合。有公共特征的对象可以向上提取一个接口类,然后统一管理这些对象。要避免出现面向过程式的复杂的逻辑判断。对于复杂度过高的方法或类要及时修改。
-
此外还学习到了正则表达式的应用,java的各种容器的使用,巩固了java编程基础,增加了代码能力。正则表达式不要太长,不然会爆栈,可以使用层次化的正则表达式。
-
做的不足的地方是,没有尝试合并同类项,也没有尝试用三角函数公式化简,也没有括号展开,也没有写递归下降。
-
自动评测系统不够完备,无法生成极限数据。
-
多和同学讨论,多看讨论区,可以开阔自己的思路,有助于构建自己的程序架构。

浙公网安备 33010602011771号