HW1
第一次作业主要是主要是完成对包含 "+,-,*,**,()" 的表达式进行拆括号并化简的过程。
1.1 UML图
1.2 类复杂度分析
OCavg
: Cyclomatic Complexity of a Class
OCmax
: Maximum operation complexity
WMC
: Weighted Methods for Class
可以看出我的Term
类和Parser
类的复杂度很高,这是因为我将所有的解析(包括乘法乘方,多余正负号的消去)都放在了parser,所以parser的复杂度最高;而对于解析完的计算,我主要放在Term里面完成,所以其复杂度也很高。
1.3 方法复杂度分析
CogC
:Cognitive complexity
ev(G)
:Essential cyclomatic complexity
iv(G)
:Design complexity
v(G)
:Cyclomatic complexity
这里可以看出在Term
和Parser
中方法复杂度远大于其他类,正好映证类复杂度中的分析
1.4 bug & bug修复
-+x
我对一开始的多个正负号没有处理好,一开始的多个正负号我只会读取离表达式最近的正负号,导致了强测寄了一个点(被分到了b房≧ ﹏ ≦),互测也被hack烂了;但万幸的是只有这一个bug。
HW2
第二次作业跨度很大,加入了三角函数、自定义函数和求和函数。由于加入的因素我在第一次作业中我没有预留空间,所以第二次作业我选择了重构。
2.1 UML图
2.2 类复杂度分析
重构后,Term
类完成所有的乘法以及乘方运算,并且内有合并同类项的运算方法,因此复杂度很高;parser
不必说,解析的,同hw1复杂度高;preprocess
类是用来预解析输入表达式的,这个类干的事就是把所有的求和函数、自定函数替换为hw1的输入形式,因此复杂度很高。
2.3 方法复杂度分析
Func
类的Func
函数复杂是因为,这个函数对自定义函数解析,解析出形参和表达式,所以略微有些复杂。Parser
类的几个函数不用多说,承担着艰巨的解析的任务。Preprocess
的assignReal
函数是为了找到表达式中自定义函数的实参,也是解析,所以复杂度略高。
2.4 bug & bug修复
其实有三个bug,但是有一个到hw3才测出来,所以在这儿只说前两个bug
- 求和函数下限大于上限时,输出0
没看到教程中这个要求,导致没考虑这个,还好强测没测+互测没被发现)
- 在
Preprocess
替换自定义函数的时候,并未判断最后的表达式中是否存在要替换的自定义函数,而是直接循环遍历所有存储的自定义函数,导致强测错了两个点(太惨了)
HW3
这次相较于hw2,放开了三角函数括号里面内容以及求和函数里面表达式的限制,但其实也改的不多,所以这是唯一一次进a房了)
3.1 UML图
3.2 类复杂度分析
由于基本同hw2,所以分析略
3.3 方法复杂度分析
同hw2
3.4 bug & bug分析
这次的互测就极具戏剧性,在oo大群和6系水群中有人问**数据为啥不合法(其实是合法的),有两个数据刚好是我的bug,由此导致了我互测的悲剧(受到hack:16/40,你敢信?)
sin((-x))
这次要求,如果三角函数内部不是x**b或者不是常数的话就要再加一层括号。我当时是判断哪些要加括号,而不是判断哪些不用(太蠢了😢);而我在判断a*x**b的时候a我只判断了正的,忘了a还可能是负的,悲剧发生。
- 在hw2中未发现的bug:求和函数中的数可能大于int!我原先求和函数那儿是用int存储的上下限,hw2也没这方面的测试。结果hw3的时候有人一开始就问这个数据,然后就发现自己存在爆int的问题,悲剧二连。
三次作业架构设计体验
4.1 架构迭代
4.1.1 HW1
刚看到第一次作业时是很懵逼的,完全不知道大体框架应该如何搭建,知道第二天助教发布train,真的是救人于水火,让我知道了还有递归下降这样神奇但十分符合逻辑的架构,由此定下接下来三次作业的主基调——递归下降。
- 由于最后化成的结果必然为
a*xb
,因此我将最底层的类Number
的属性设为存储a
和b
并实现了Factor
接口,这样就可以以a*xb
的形式来输出。 - 对于上层的类
Expr
(实现了Factor
),HW1中只完成多个hashmap的Factor
的加法合并,最终生成一个完全化简好的hashmap。 - 对于
Term
类,HW1中完成Number
和Expr
的混合乘法计算,并化简为一个hashmap的Factor
。(这里就很不符合递归下降的思想, 递归应该是Expr
->Term
->Number
->Term
->Expr
,我这儿中间相当于没有一个阶段存储Term
类的,埋下了HW2重构的苦果) - 对于多个正负号,在
parseExpr
阶段就消去,用一个boolean变量保存最后的正负号,传到parseTerm
,在parseTerm
中加上正负号。 - 很多同学是在第二次遇到了深拷贝的问题,导致写第二次的时候十分痛苦,我就不一样,我第一次就遇到了😭,不能用普通的“=”来对一个大的类进行赋值,而应该循环遍历到底层进行赋值,否则只是指针的赋值。
4.1.2 HW2
第二次看到加入那么多元素的时候就知道自己寄了,要重构。所以从第二天就开始重构,重构结构如下:
Number
类属性不变,重写了一些方法。Term
类属性变成三个,一个Arraylist存所有未化简的Factor
,一个Number
存化简后一项对应a*xb
,一个Arraylist存一项中所有的三角函数。Expr
类属性变为两个,一个Arraylist存所有未化简的Term
,一个Arraylist存化简后的Term
。parser
类中,parseFactor
返回Factor
,parseTerm
返回一个Arraylist的Term
,parseExpr
返回一个Expr
,这样就符合Expr->Term->Facor->Term->Expr的递归逻辑。
对于第二次加入的三角函数,我增加了一个Trigo
类(实现Factor
接口),里面实现三角函数的各种操作。对于自定义函数和求和函数,我采用字符串替换的方法(助教和老师都不推荐的方法),在输入parser前将表达式里面的自定义函数和求和函数替换为能计算化简的表达式。在替换自定义函数时有个小trick:对于形参x、y、z,采取先替换x、再替换y、再替换z,这样就不会出现后误替换的操作了。
4.1.3 HW3
在第三次本来很忐忑,因为助教说用字符串替换的第三次可能很难做,我都做好了重构的准备了,结果看到题的时候发现,字符串替换还能用!!!(感谢助教没有提高难度)第三次我做出架构上的更改如下:
Trigo
类中代表三角函数括号内的属性由Number
改为Expr
(上次说三角函数里面是只可能是Number
所以设为Number
)Preprocess
里面对自定义函数的替换采用递归替换(因为这次又自定义函数的嵌套)
4.2 关于hack
hack跟课下找bug策略一样,先用数据自动生成器生成合法数据,稍稍修改MainClass类让其能无限读入,然后批量喂入数据,将得到的数据与先前生成的合法数据一起放入sympy进行比较。这三周的hack让我深刻体会到了6系的卷😵。而且由于第三周被同学分享在群里面的数据坑惨了,真心希望同学们别在群里面问数据合不合法了,私聊问吧。
4.3 体会与感悟
oo课让我第一次体会到写代码能有多难,bug能有多离谱(oo课之难,其他同学也叹为观止,说你们这是在上编译原理吗)。前三次让我感触最深的就是第一次到第二次的过程:第一次的时候对整个题目的理解没有很到位,没有理解透彻递归下降的意义是什么,写了一版不伦不类的递归下降,这直接导致我第二次作业时痛苦的重构,直到第二次我才深刻认识到递归下降应该是什么样的,明白有些优化是不应该在框架中做的,那样会破坏结构的完整性。
由于前两次都在b房,我对优化也有了深刻的认识:宁肯不优化,也要保证正确性;优化的得分相较于正确的得分,还是较小的;要保证一定的正确的情况下,再去优化,希望自己后面努努力能多进进a房。
最后感谢助我debug的同学、助教,感谢给我讲解框架的同学,没有你们我就苟不过这三周。最后的最后,电梯月,求别太虐。