第一单元总结:表达式的解析
第一单元的三次迭代终于完成,在三周时间的代码训练中,我初步掌握了表达式解析的有关方法,对解析时所常用的架构有了更深的了解,
本单元作业中具体如下:
目标简介
作业一:通过正则表达式或递归下降方式,识别表达式、项、因子,因子包括常数、幂函数、表达式,因而进一步去除括号。
作业二:同样去除表达式括号,但是输入正式的表达式前先读入n个自定义函数(fgh),并且引入exp指数函数,指数函数类似幂函数的结构,也可带指数。
作业三:支持求导操作,新增求导算子,同时作业函数表达式中支持调用其他“已定义的”函数.
个人具体构建过程
分析维度
用MetricReloaded分析方法和类复杂度时,几个维度含义如下
CogC :关于理解方面
着重从阅读的角度来看,将一段代码复杂程度估算成一个具体数字,多次的重复遍历和嵌套将提升复杂度。
ev(G) :关于结构方面
是基本复杂度,来衡量程序非结构化程度,非结构成分降低了程序的质量,增加了代码的维护难度,同时也导致debug难度上升
iv(G):关于模块方面
是模块设计复杂度,来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,所以一般小于圈复杂度
v(G) : 关于测试方面
用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
类的复杂度:
OCavg :平均操作复杂度
OCmax:最大操作复杂度
WMC : 加权方法复杂度
第一次作业
主要思路
储存数据类
第一作业框架的构建十分困难,关键是从何入手。再纠结不知多久之后,根据题目引导的架构
本人首先先建立表达式、项、因子三个类,将因子类设为虚类,并使表达式类继续继承因子类。这样就构建好了具体实现的存储类,每个对象中存放对应的数据。
解析类
之后就要考虑这些类实现的方法。首先应当选用递归下降进行解析,因为根据本人理解来看,递归下降更为灵活(虽然本次不要求括号嵌套,但是在后续迭代中,如果继续使用正则表达式匹配将非常棘手)
递归下降时,解析表达式就是向表达式类中添加各个项的过程,而解析项就是向项类中添加各个因子的过程。表达式的建立是一个动态的过程,每添加一个项就产生一个静态的表达式。
括号化简方法
化简应当伴于添加左右,即一边添加因子或项一边化简。
在表达式中添加项之时,可分两种情况讨论
- 添加的是无表达式项,则直接添加,同类项则合并
- 添加的是有表达式的项,则先将项中的每个表达式相乘,得到的表达式再与项中其他的因子相乘展开,得到一个项的hashset,再将每个项依次加入,同类项则合并
在项中添加因子就较简单了,若为数字则系数相乘,若为幂函数则与已有幂函数指数相加
细节问题
主要的细节问题出现在卷提高性能分方面,总结来说有以下几点
表达式中
- 首项若是正项则不显示加号
- 项若是0则不显示
项中
- 系数为1则不显示
- 系数为-1则直接加负号
另外还有同学说如果有正项优先把正项放第一个,有时能省一个正号
bug分析
第一次作业遇到的bug不多,且多为疏忽笔误导致,最后强测没有出问题。
印象最深的错误是添加项或因子时把原来的对象加进去了,要注意深拷贝一个对象再加入,否则会出现各种奇奇怪怪的错误()
hack方面
本次作业hack并未成功,在尝试hack中会尽量考虑边界值的特殊情况,例如00,x8等
类图
由如上说明构成类图
数据存储类:
解析类和预处理类:
复杂度
可以看到add相关即添加因子或项以及化简相关方法较为复杂,这几个方法具有很多多层嵌套循环,在处理中不断相互递归调用,因而复杂度比较高
可以看到parse和Term中复杂度较高,主要因为parse是解析类,因而集成了递归下降算法的核心。Term中作为将Factor与Expr相连接的类,本人将很多处理及化简Term中因子的方法加到了Term中,又分了很多种情况讨论处理,最后导致Term类较为臃肿,可以考虑将化简等方法单独拎出构成新的类,降低复杂度
第二次作业
主要思路
存储数据类
- 对于自定义函数,新增Definefunc类,存储每个自定义函数的名字、函数体、变量,其中变量和函数体都用String类存储,便于之后调用实参处理
- 对于指数函数,新增Pow类,专门存储指数函数因子。其中变量用Factor存储,在存入时首先为Expr,之后再判断是否能化简,转化为数字或幂函数,这样可以省一个括号。
解析类
大体框架改的不多,主要新增对函数因子和指数因子的判断
函数调用方法
首先将形参对应转化为“a-c“,以为了防止之后替换实参时错误替换,再将”a-c"转化为实参,注意在两边加括号,其中exp符号单独转化,防止x也被替换
细节问题
指数函数的优化
个人认为这是本次作业最能体现代码层次结构清晰度的地方,我的优化方案总体如下
term中加指数函数时:
- 如果碰到原本已有相同指数的指数函数,则将内部变量相加合并
- 如果碰到原本已有相同变量的指数函数,则将指数相加合并
Expr中加term时:
- 新增equal函数以便于判断是否是同类项,equal函数时Factor的重写方法,可以递归判断两个Term中的Exp、Pow是否相等,如果都相等则判断为同类项,可以系数相加合并
还有个人没有实现的部分,判断exp内部是否有共同Number因子,可以提出来放到指数上,当数字很大时可以省很多空间。
bug分析
强测惨不忍睹,最后改完发现有多达四个地方有问题。
- toString方法的重写,导致程序在运行和调试时出现不一样的情况。
- 在exp解析内部变量时,采用的是parseFactor,这样导致之后转化为字符串化简时如果为带幂次的Expr,则幂次打印不出来,应该保险改为parseExpr。
- 判断两个项是否为同类项时,只判断了第一个项中的Exp和Pow在第二个项中有就直接判为相等,导致合并同类项错误,应该遵循相等的如下原则:第一个有的第二个有,第二个有的第一个也有。
- 还是对象克隆的问题,在添加新的exp时,应当新建一个对象
原因感觉还是粗心导致,另外应当充分利用java的clone类,否则自己每次手动构造一个新的类难免会忘记或者出问题
类图
由如上说明生成类图
数据存储类:
解析类和预处理类:
而由预处理得到自定义函数类的一个Hashset,,funcset:
复杂度
除了第一次作业化简相关方法复杂度较高以外,对函数因子传递的parseFuncFactor方法也较为复杂,因为需要分别处理读入实参等数据,处理起来比较冗长。
此外用于equal方法如上述所说是为了判断是否为同类项,递归的调用导致系统有额外的开销,属于是牺牲性能换性能分()。
依旧是熟悉的parse、Term,此外由于判断同类项的出现,Expr中每出现一个新Term就需要嵌套循环每个Term及其每个因子,实现起来比较复杂。
第三次作业
主要思路
这次作业要添加的不算多,只要新增一个Derivator类,用于返回一个表达式求导的结果即可。
求导方法
从底层开始
- 对Factor求导这里不会出现Expr的Factor,所以直接套用公式就行
- 对Term求导时,遍历Factor,对其中一个求导再乘上其他Factor构成一个新的Term,最终返回的应该是一个Expr
- 对Expr求导时,遍历Term,将每个Term求导的结果合并得到一个新的Expr
如此一来通过遍历可较为清晰展现
bug分析
竟然是之前遗留下来的bug,对自定义函数的每个实参进行解析时,用的是parseFactor,和第二次作业exp内部解析bug是同个问题,应当改为parseExpr(),而且之前的强测也没测出来。
原因还是自己数据构造不够充分,甚至没有考虑到实参中有带幂次的表达式因子的情况。
hack方面
因为这次作业不同于以往的只是求导和函数定义时的调用,因此测试时多考虑有三个函数的调用定义情况
而由于求导因子存在的位置比较多,可以尝试在函数实参内、exp变量内出现,并且求导的表达式可以涵盖多个表达式因子、幂函数、指数函数混合的情况。
类图
数据存储类相同,解析类中只多加一个Derivator,具体在上文已说明:
复杂度
因为其他类与方法与第二次作业基本无区别,在这里只给出Derivator相关复杂度。
由于其本身又是以嵌套调用为基本方法的类,因而每个方法并不复杂,但是对于求导时调用的整个类的方法来说依然较为复杂。
心得体会
这个单元的三次作业做得很艰难,遇到的bug也很多,但正是因为这样的困难也让我对面向对象编程的思维方式逐渐熟练起来,主要体会有以下几点:
-
一个好的架构永远是第一位的。在第一周时碰到全新的需求和问题时完全就是一脸茫然,刚开始的设计是最难的,也必须要为之后两次作业的扩展做好铺垫。而未经思考的下手往往会导致之后不必要的重构,因此为了设计的可扩展性,必须先有对整体的思考,然后再在构造时不断优化,如此才能提高效率。
-
写完代码后要充分进行测试,可以搭建评测机加快测试。例如个人第二次的bug中,关于合并同类项时判断错误的问题,如果能测试到位完全不用到了强测之后才得以被发现。
-
要充分利用讨论区,有好的思路应该分享,并及时发现讨论区中优秀的想法或者架构,必要时可以参考而进行重构
到全新的需求和问题时完全就是一脸茫然,刚开始的设计是最难的,也必须要为之后两次作业的扩展做好铺垫。而未经思考的下手往往会导致之后不必要的重构,因此为了设计的可扩展性,必须先有对整体的思考,然后再在构造时不断优化,如此才能提高效率。
-
写完代码后要充分进行测试,可以搭建评测机加快测试。例如个人第二次的bug中,关于合并同类项时判断错误的问题,如果能测试到位完全不用到了强测之后才得以被发现。
-
要充分利用讨论区,有好的思路应该分享,并及时发现讨论区中优秀的想法或者架构,必要时可以参考而进行重构