2024OO Unit1单元总结

尹祺霖-22371157 学生 2024-03-20 16:59:24

OO Unit1:表达式解析和递归下降的应用

一、Unit1简述

在第一单元的任务中,需要我们对于一个给定的复杂表达式进行一定程度的预处理之后进行递归下降表达式解析,之后根据解析出的结果来输出保留必要括号的形式。

二、三次任务的要求

第一次作业

第一次作业包含的因子较少只有三种

img

第二次作业

第二次作业增加了自定义函数和指数函数因子,指数函数的括号里面是因子

img

第三次作业

第三次作业新增了求导和自定义函数的已知的嵌套调用

img

三、设计思路

我的设计思路是先对于输入的字符串进行一定的预处理,预处理之后将字符串传入Parser类进行解析,运用递归下降方法,解析后的结果是一个Expression类变量,之后进行计算(也是去括号的过程),然后会得到计算后的Expression变量,最终还有个printer类负责输出。

UML类图

img

首先从main类中获得自定义函数的数量,然后接收后将自定义函数放到Function类中。

Function类

Function类包含了自变量数量,函数表达式,以及自变量的先后顺序。

img

Lexer类

记录下了自定义函数后我们将记录的自定义函数传入Lexer类,之后对字符串进行预处理,预处理包含了对于空格和水平制表符进行处理,对于连续的+-符号进行处理,把-变成-1*,如果首项是+或-变成0+或者0-,替换自定义函数等等。
部分代码如下图所示

img

替换自定义函数

img

Lexer类包含了next(),getNumber(),以及peek()等方法,主要从课程组给的第一单元训练代码中copy下来的,用来提供curToken来供Parser进行解析。

Parser类

我们将定义的Lexer变量传入Parser类,Parser根据Lexer提供的curToken进行解析,包含表达式解析,项解析以及因子解析,代码和课程组发的第一单元训练代码一个思路。部分代码如下图所示

img

Expr文件夹

Expr文件夹下包含了Expression,Term,Var,Num,Exponent,Derivation等等,因子为Factor接口。

递归下降解析完后我们得到了一个存储了解析信息的Expression变量,之后我们需要进行计算,因此我们提取出了标准项类Standard

Standard

解析完的表达式可以看作是一棵树,每个节点的价值便是标准项的Arraylist,第一次作业可以用Hashmap来计算每个节点的价值,但是之后无法支撑第二次作业要求,因此提取了标准项来表示。部分代码如下

img

Expression,Term等类

Expression,Term等类首先是他们的成员变量,包含了Arraylist Standard和对标给的指导书就行。部分代码如下

img

其中对于指数的处理我们可以在预处理就展开,也可以在计算的时候在处理,这里我选择的是后者因此在表达式类中包含了index作为表达式的指数。

之后就是计算了,计算我是在Expression,Term等类中设置了calValue方法,Var的calValue代码如下

img

计算的过程也是递归的过程,可以看到计算的结果就是Standard的Arraylist,这也代表了树的每个节点的价值。

Calculation

对于通用的计算,我采用了单例模式,比如两个Arraylist Standard的相乘,相加,相减(第一次作业没写,后来改的),计算导数,部分代码如图所示。

img

而其中比较让人头疼的是我们如何去合并,第一次作业比较简单,只需要看指数是否相同,而之后的作业则是需要专门写一个来看是否符合合并的要求,满足exp里面的因子完全相等和x的指数相等才满足合并条件。下图分别是合并和判断是否完全相等。

img

img

Printer

计算完了之后,我们需要最终打印输出,这部分也是递归调用,我是用StringBuilder来操作的,注意的是第一个如果是+号则去除以及简单的优化比如1*x写为x。

四、架构设计

第一次作业架构

第一次作业的架构图如下

img

我在第二次作业时经历了重构,相比于第一次作业的架构我提取了Calculation来提供计算方法,以及我提取出了标准项而不是像第一次作业一样简单的用Hashmap来计算,以及我创建了一个Printer类来负责输出。

第二次与第三次作业架构

第二次作业经历了重构后我的架构基本定型,第三次作业在第二次的架构上增添完成。

架构优缺点分析与新情景设想

我认为我的架构的缺点在于预处理时涉及的情况太多太杂容易疏忽疏漏,特别是在预处理时替换自定义表达式的时候,有很多的情况需要处理,感觉面向过程的思想还存留许多。优点呢是比较能体现面向对象的,各个类的分工是比较明确的。

对于新的场景的话,如果增加了因子则是接上因子接口然后写他的calValue就行,然后还需要改写Lexer和Parser,扩展性还是比较强的。

五、其他优化

完全没涉及,正确性优先,而且评论区大佬的提取公因式啥的看着也挺麻烦的也不一定能优化。

六、度量分析

img

我们可以看到Calculation和Lexer和Printer的复杂度比较高,Lexer的复杂度比较高可能是因为在预处理的时候展开自定义函数的过程写的比较复杂导致的,而Calculation的复杂度较高是因为进行了多次遍历来计算导致的,而Printer的复杂度较高是因为多次循环来输出最终结果。

七、代码规模

img

img

八、Bug以及Hack策略

hw1

hw1中我的Bug是未在预处理的时候处理连续的三个符号,导致了强测寄了3个点都是RE,互测也因为这一点被狠狠地刀了。
圈复杂度的话这个出错的方法很高

img

hw2

hw2是bug最多的一次作业,其中最大的一个是统计自定义函数的自变量数量时忘记了去掉空格和制表符导致强测寄了8个点,特别惨痛的教训,一定要认真读题,我应该养成读题的时候记录要点的习惯然后再开始写,还有一个bug是没有用BigInteger存储x的指数,也是读题不充分,思考不充分导致的问题。仔细读题的重要性可见一斑。

img

这张图可以看出这个经常出bug的方法圈复杂度很高

hw3

hw3的bug是hw2遗留下来的,在处理自定义函数展开的时候的bug,这个bug还是因为测试不够充分的原因。

Hack策略和有效性

第二次作业我未进入互测,第一次作业和第三次作业我采用的互测策略是先构造一个包含了多个可能错的点的数据交上去,如果能刀中人,就把其中的可能错的点单独交上去,也就是说两次刀中基本上就可以确定被刀中的人的bug。两次作业的互测一共刀中了6刀。

九、心得体会

OO第一单元作业真是让我挺感慨的,第一次作业当时一直搞不明白怎么去遍历解析出来的树去得到最终的结果,经过朋友的指导后才明白可以用Hashmap去递归计算,第二次作业发现不能用Hashmap之后我想出了提取出了Standard类来表示标准的单项式,并且把混杂在Expression和Term中的加法,减法,乘法提取出来单独作为Calculation类中的方法来供所有类使用,我觉得这是逐渐走向面向对象,让各个类的分工走向明确的做法,但是我觉得在第二次作业中我对于自定义函数的处理还是面向过程的做法,或许把他带到递归下降解析中会好一些,第三次作业比较简单,完成得也挺快,我在第三次作业中弥补了第二次作业中没有合并的缺失。

从这个单元的完成中,我觉得我的一个很大的转变是我本来是一个完美主义者,我会对我的设计想的很仔细很清楚才会动手,但是hw2给了我一记重击,我明白了想个大概就得动手写了,很多细节在写的过程中才能明白不是空想能够明白的,我觉得在每个单元的第一次作业对于架构的设计中可以画图多花一些时间考虑扩展性,但是对于看起来比较复杂的扩展任务(比如hw2)我应该想个大概的思路就开始动手,不要拘泥于细节,写着就明白了,正所谓“船到桥头自然直”。

这个单元我留下了许多的遗憾,比如没有怎么去考虑优化,性能分就没拿多少,比如两次互测都是c房(也基本是性能分过低导致的),读题的不仔细导致了强测的遗憾。包括从心态上更多是完成作业后劫后余生的情形,没有很多感受到完成目的成就感,我想我或许可以在以后的单元少想细节多动手,或许会让我能够切换心态,感受OO的乐趣。顶个小目标,第二单元至少进2次a房,在优化上多下功夫,加油!!!

十、未来方向

在第一单元中我目前感觉给我有两大困惑,首先是感觉理论课和实践并没有太大的联系,完成作业主要还是参考指导书和往届学长的博客,理论课并没有很大的帮助。第二个是第一单元的任务让人感觉和实际的联系不大(或许是培养我们的面向对象的思想,初步感受一下),千奇百怪的表达式的解析给我的感觉是用处不是很大。以上只是个人对第一单元的感受,或许可以用作课程改变的参考。

...全文
146 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧