OO-Unit1-表达式解析

申剑璋-22373372 学生 2024-03-21 15:56:26

目录

 

第一次作业

目标

基本思路

UML类图

代码规模

复杂度分析 

 方法复杂分析

类复杂度分析 

 架构分析

优化处理

Bug分析

第二次作业

目标

迭代思路

UML类图

代码规模 

复杂度分析

 方法复杂度分析

类复杂度分析

​ 

架构分析

优化处理

Bug分析

第三次作业

目标

迭代思路

UML类图

代码规模

复杂度分析

方法复杂度分析

​ ​ ​

  类复杂度分析

架构分析

优化处理

Bug分析

心得体会

未来方向


第一次作业

 

目标

读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式。

 

基本思路

  • 表达式预处理:由于输入的表达式中含有不定数量的空白符,并且还会出现连续的加减号。因此,先对输入的表达式字符串进行预处理(删除不必要的空白符和加减符号)能够使之后的表达式解析更加方便。

  • 表达式解析:根据相关的数学知识,我们可以将表达式(Expr)按照 + 、 - 分为多个项(Term),而每个项按照 * 分为多个因子(Factor),对于因子我们可以将其分为3类——常数因子、幂函数因子(x^b,其中b为常数)、表达式因子。其中对于表达式因子的处理,采用递归的方式再次调用处理表达式的方式即可。在理解表达式的递归表达通式之后,借助 Parse 和 Lexer 将字符串表达式解析成Expr的形式。

  • Expr转换:为了方便输出,我们将 Expr 都转换成多项式(Poly)。而 Poly 的基本表现形式为——a*x^b(a为系数,b为指数),因此借助Hashmap<BigInterger,BigInterger>这样一个容器储存多项式,其中 key 代表指数, value 代表系数。得到这样一个 Hashmap 之后通过计算将表达式进行化简,最后将结果进行转化输出。

 

UML类图

 

代码规模

 

复杂度分析 

 方法复杂分析

类复杂度分析 

 架构分析

  • 采用递归下降的方法对表达式进行了解析,方便后续处理表达式嵌套的情况。
  • 将表达式转换成多项式Poly,借助Hashap<BigInterger, BigInterger>这一容器进行统一管理,便于后续的计算和化简输出。

优化处理

对于第一次作业可以优化的部分有限,需要注意的点如下:

  • 对于系数为1或-1的poly的处理。
  • 当指数为0或1时,poly输出的处理。
  • 若最终化简的多项式中有正项,则将其放置在第一位可以节省一个字符。

Bug分析

本人在测试的过程中没有遇到bug,但是在互测环节中发现有部分同学对于最终输出为0的表达式的处理出现问题。这部分同学由于最后计算结果为0,导致输出空串造成bug。

 

第二次作业

目标

在第一次作业的基础上,进行功能的扩展。增加的迭代要求如下:

  • 本次作业支持嵌套多层括号。
  • 本次作业新增指数函数因子,指数函数括号内部包含任意因子。
  • 本次作业新增自定义函数因子,但自定义函数的函数表达式中不会调用其他自定义函数。

 

迭代思路

  • 对于自定义函数的处理,本人将其储存在一个Hashmap<String,String>的容器中。Key包括了函数名以及使用的参数,Value则是对应的函数表达式。在解析表达式的过程中,如果遇到了自定义函数因子则将对应的表达式替换进去,然后再按照第一次作业的思路完成解析即可。
  • 对于新增的指数函数因子,需要将exp()括号的因子调用parseFactor方法来解析即可,同时要注意exp()可以有嵌套。此外,此次迭代需要修改基本储存单元,因此这次作业增加了Unit类用于管理基本储存单元。Unit中的属性为coe(系数)、index(指数)和Arraylist<Unit>(exp()括号内的因子)。 

 

UML类图

 

代码规模

 

复杂度分析

 方法复杂度分析

类复杂度分析

 

 

架构分析

  •  就第二次迭代,架构部分修改了基本的储存单元,此部分在迭代思路部分已经阐述。
  • 新增加了指数因子类用于处理指数函数部分。
  • 此架构并没有新增函数因子,而是在解析的过程中将其替换成了表达式因子,从而减少了迭代的工作量。
  •  

优化处理

在第一次作业的基础上,第二次作业的优化难度更大,本人所想到的优化点如下:

  • 指数因子的括号层数是否多余,exp()内只要是因子即可满足格式化输出。因此要保证括号层数尽可能的少,例如exp(x)要优于exp((x))。
  • 当exp()内为0是,要将最后的输出即为1。
  • exp()内部是否存在可以提出的公因子,如果有则可以选择将其提出作为exp()的指数部分,但是该操作不一定保证会让字符串的长度变短,因此要将未提取公因子的最终表达式于提取后的作比较,观察该操作是否起到了优化效果。
  •  

Bug分析

第二次作业的强测环节,代码出现了TLE的问题。后经过检查发现是进行运算时没有对乘方运算的处理出现了问题,我对于乘方的处理就是若解析到 '^ a' 则将a个相同的因子放入项中储存,这就导致当处理类似 (((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8 的极端数据时,调用乘法运算的次数呈指数级增长,最终造成Time Limited Error。因此,针对这个问题我修改了处理乘方符号的方法,我将这个指数作为一个属性单独存了起来,等到需要运算的时候再将因子展开做乘法,这样调用乘法的次数就是一个平稳的线性增长,不会出现之前那种恐怖的增长幅度。

 

第三次作业

目标

在之前迭代的基础上增加了求导因子,同时自定义函数的函数表达式中可以调用其他自定义函数。

 

迭代思路

  • 自定义函数的函数表达式中可以调用其他自定义函数这一点在第二次迭代的过程中就已经解决,因为本人用因子替换形参时采用了递归替换的方法,故这一功能已经得到了实现。
  • 本次迭代的重点就在于如何处理求导因子,本人采用的思路就是在解析的过程中完成求导,使其转换成第二次作业的形式。这样迭代的好处在于原本的构架不需要更改,只需要增加一个Derivative类用于转换求导因子即可,避免了对原有代码的修改。处理求导因子的过程并不复杂,只需要清楚因子求导会变成一个项,而项求导会变成表达式,表达式求导依然为表达式。在迭代的过程中需要的注意就是求导时表达式、项和因子的深浅克隆问题,如果没有采用深克隆,则极有可能会导致因子发生意料之外的变化。故为避免出现这一问题,建议每当调用方法时需要进行参数对象传递就将该对象进行深克隆。
  •  

UML类图

 

代码规模

 

复杂度分析

方法复杂度分析

  

  类复杂度分析

 

架构分析

只新增了Derivative类用于处理求导因子,将其转换成了Expr形式,避免了对已有代码进行修改而带来其他的bug。

 

优化处理

与第二次作业的优化处理方式基本上一致,此处不再赘述。

 

Bug分析

本人的代码顺利的通过了强测以及互测,但是从评论区得知有同学出现了CPU处理时间超时的问题,例如处理类似  dx(exp(exp(exp(exp(exp(exp(exp(exp(x^2))))))))) 的数据。猜测是处理递归的过程中没有及时将基本单元合并,导致在进行计算的时候递归次数过多造成CPU计算时间过长。

 

心得体会

在完成整个Unit1的作业之后,我对于面向对象编程的理解更加深入了。同时也清楚地认识到一个条理清晰、逻辑顺畅的架构对于整个Unit开发和迭代的重要性。在第一次作业的开发过程中,我并没有太注重整体架构的可延展性,这就导致了我第二次作业的推进变得十分艰难,最终只将表达式的计算部分进行小范围的重构。因此,我便意识到一个好的架构不仅能够让代码看起来思路更清晰,同时还能大大减轻功能迭代所带来的工作量。

此外,对于自动评测机的搭建,本人只是完成了第一次作业的评测机搭建,之后两次并没有对评测机进行更新迭代。但是我也收获颇丰,因为完成数据生成器的过程让我对于指导书的理解更加深入,能更好地避免出现理解指导书不到位的问题。

 

未来方向

我认为这一单元课程内容的难度还是比较合适的,但是对于优化问题的处理感觉收益不大。建议在之后的课程中可以让代码的可优化性变得更强。

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

301

社区成员

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

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