BUAA-OO-Unit1总结

刘志千-22373024 学生 2024-03-20 22:14:30

BUAA-OO-Unit1总结

1. 题目简析

本单元作业的要求大体上可以概括为,给定一个单变量表达式,我们需要对它进行去除括号,化简计算等操作,得到一个恒等的表达式,在每次任务迭代中,我们逐步添加了自定义函数,指数函数,求导因子等更多更复杂的功能。具体的实现过程可以划分成以下三个步骤:

  • 表达式解析。主要是首先使用Lexer类做词法分析,将表达式中的符号拆分成一个个的最小单元Token;然后使用Paeser类做语法分析,根据规定好的形式化表述,将表达式构建成一棵语法树。
  • 表达式计算。该步骤主要依托于单项式类Monomial和多项式类Polynomial,通过深度优先遍历语法树的方法,将表达式变成具有固定格式的单项式的加减,同时固定的形式也为进行加减乘的计算和合并同类项提供了方便。
  • 表达式输出。主要是通过单项式和多项式类中的toString方法,将最终结果的多项式输出成字符串,其中还包括了表达式长度的优化。

虽然题目经过了几次迭代,但我处理问题的思路一直是这三个步骤,根据高内聚,低耦合的原则,对于以上的三个步骤,我尽可能保证每个类都只完成其中的一个功能,不同的过程由不同的类来完成,通过这种方法,提高程序的可扩展性,每次添加新的功能的时候,尽可能不去修改原有的代码。

2. 架构迭代

2.1 HW1

img

2.1.1 表达式解析

在第一次作业之中,我使用Lexer的类来帮我们将整个表达式拆分成一个个的Token,为下一步我们分析表达式的层次结构做好了准备工作。在做词法分析生成Token之前,我们可以做一下小小的预处理工作,包括但不限于删去表达式中的空白字符,合并表达式中出现的连续的加减符号(可以证明,这样做并不会改变表达式的值,对下一步的语法分析也大有裨益)。

然后,我使用Parser类,按照了如上图左侧的Expr--Term--Factor的结构构建出了语法树。在这样的树中,由上到下分别有表达式Expr,项Term,因子Factor,表达式由项用加减符号连接而成,项由因子用乘号连接而成。因子中还包括了三类,常数因子Num,变量因子(目前仅有幂函数)Pow,表达式因子ExpFac。我设计一个Factor接口,来统一管理这三类因子,其中,表达式因子中一定还有表达式这一组成成分。

2.1.2 表达式的计算与输出

可以发现,最终的表达式的形式为:

img

基于此,我建立起多项式类Polynomial来存储计算出的结果,其中包含了一个用幂函数的指数pi来索引的HashMap,通过实现多项式的加减乘方法,来完成对表达式的计算,并将表达式输出。

2.2 HW2

img

在第二次作业中新增了括号嵌套、指数函数因子和自定义函数因子,其中,括号嵌套不需要修改原先代码即可实现。

2.2.1 表达式解析

对于指数函数因子来说,我只需要新增一个Exp类,并且像其它的因子类一样,实现Factor接口,并修改Parser类,使其能解析新增的因子类型即可。

自定义函数因子的相关功能包括函数的定义和函数的调用,因为函数定义式的解析和我们待处理的表达式的形式是几乎一样的,所以我决定复用LexerParser类,同样将函数表达式处理成语法树的格式。具体的方法是构建一个类Function作为自定义函数的模板,负责解析并存储函数定义式,在该类中存有函数的形参,函数定义式解析完成的语法树。在函数调用的时候,通过函数名识别出对应的函数模板,将模板的语法树深拷贝到自定义函数因子中(通过Expr,Term,Factor的substitute方法来实现),并将语法树中的形参替换成实参因子(实际上需要替换的只是幂函数),这种方法就像是在自定义函数因子这样的叶节点下继续接上一棵语法树。

2.2.2 表达式计算与输出

本次作业中,最终表达式的格式变成了:

img

我最终修改了多项式类Polynomial,并且新增单项式类Monomial来进行表达式计算。二者相互进行定义。对于单项式中的三个属性,ratio:BigIngeter表示这一项的参数,index:BigInteger表示这一项中幂函数的指数,exp:Polynomial表示这一项中指数函数的指数,指数函数外层的幂次统一乘到括号里面。而对于多项式,其中就是使用indexexp进行索引的一个二维的HashMap,里面存储了该多项式中的所有单项式。单项式和多项式相互包含,相互定义,可能这有点难理解。整个生成过程大概是这样定义的:首先有一个空的Polynomial,它可以用来生成不含指数函数的Monomial,再由这样的Monomial生成各种各样的Polynomial

在进行合并同类项的时候,需要将仅仅系数不同的两个单项式进行合并,因此需要判断指数函数是否相同,也就是判断两个多项式是否相同。

2.3 HW3

img

第三次作业中,主要是新增了函数定义式的嵌套和求导因子。

在第三次作业中,代码的整体框架几乎没有太大变化,函数定义式的嵌套继续按照上次作业的方法,我只在Main中做了一点调整,使得解析函数表达式的时候可以使用已定义的函数。对于求导因子,我新增了DxDac类,仿照表达式因子中的属性方法进行构建,最终便完成了最后一次的迭代。

假如下次迭代的时候,我们需要实现三角函数sin和cos的功能。在表达式解析的时候我会继续添加这两种不同的三角函数因子。在表达式计算的时候,我会在单项式中加上一个ArrayList或者HashMap来存储该项中的三角函数,然后求导的时候使用链式法则。至于三角函数相关的优化,可以考虑使用一些三角函数公式,不过我感觉这会是一项复杂度非常高的工作。

3. 度量程序结构

以最后一次迭代完成的代码为基础,分析复杂度并思考改进方案。

3.1 类复杂度分析

img

利用相关工具可以看出,PolynimialLexerMonomial这三个类的复杂度是比较高的。

Lexer高复杂度的原因在于其中有一个非常长的条件分支语句,用来让Lexer能够将读取到的不同的字符转化成相应的Token,在这个方法中,一共有十几个分支,这是造成该类复杂度高的主要原因,解决的方法我想到了oopre中学到过的表驱动编程模式,用HashMap建立读入字符和操作的映射关系。

PolynomialMonomial的高复杂度在于这两个类几乎承担了表达式计算和输出的全部功能,计算包括了单项式,多项式之间的加减乘,以及求导等运算,输出包括了表达式长度的优化方法以及将多项式转变成字符串的方法。可以看出这种做法其实是不太符合面向对象的原则的。如果要进一步改进的话,我会选择针对计算和输出两个功能分别建立两个静态类,它们仅仅完成计算和输出这样的单一工作,而PolynomialMonomial只实现一些存储相关的功能。

3.2 方法复杂度分析

img

以上是截取的一些复杂度比较高的方法。

Polynomial.equal是合并同类项操作时,用来比较两个多项式是否等价的,该方法首先比较两个多项式中的项数大小,然后查看多项式中的每一项在另一个多项式中能否找到相同的,在实现的时候比较复杂。Polynomial.addMono的复杂主要在于要考虑多项式中是否有单项式可以和相加的单项式进行合并,在这种情况下,实际上需要将整个多项式遍历一遍。

Lexer.Lexer方法的问题就是前面提到的Lexer类的问题。

Parser.parserFactor的复杂度在于要考虑到不同种的因子,根据读取到的Token类型确定需要的因子类型。

Monomial.toStringPolynomial.toString用于表达式输出,这两个方法由于要考虑不同形式的单项式有各自的优化方案,因此涉及到比较复杂的条件分支。Monomial.specialFormatPolynimial.specialFormatPolynimial.isFactor是做表达式优化的时候实现的方法,不得不说进行一些表达式优化确实对代码的结构造成了一些破坏,导致了类中方法的臃肿。

4. Bug分析与Hack策略

这三次作业中,我的强测和互测均未出现bug,但是在hw2的时候,我在自己测试的时候发现自己代码的一个小问题,当时使用了exp((x^2+2*x))+exp(((x+1)^2-1))这个样例,我发现无法完成合并同类项的功能,经过调试发现,第一个exp中多项式有两项,而第二个exp中有三项,多出来一个空项,显然是1-1产生的。解决方案是实现Polynimial.clearBlank方法,在进行多项式比较之前,将多项式中的空项清除。

在互测中,我在前两次作业中分别hack成功了两次,最后一次作业没有hack成功。找别人bug的方法是自己的评测机+别人开源的评测机。评测机的搭建我参考了往届博客和学长的视频,同时也吸取了讨论区同学的经验。自己的评测机依照提供的形式化定义,利用python随机数控制生成的不同类型因子,然后用生成的数据进行对拍。在互测的时候,利用评测机发现有价值的数据之后,我会将数据做进一步的化简,满足相关的要求。除此之外,我还自己编造了一些评测机不太容易生成的边界数据或者易错数据,比如x-xexp((-2*x))等。

5. 表达式长度优化

在三次作业的迭代中,我对表达式长度的优化也是在不断递进的。在hw1中,主要做了一些很基础,也比较容易实现的优化,比如尽量使得表达式开头没有运算符号,删减可以省略的幂次等等。

在hw2中,我主要去做了括号中是单项因子的指数函数的优化,也就是将exp((2*x))exp((2*exp(x)))优化成exp(x)^2exp(exp(x))^2

在hw3中,我又尝试将指数函数中各项中系数的最大公因数提出来,将修改后的表达式和原表达式进行长度比较,选择并输出较短的。

通过优化,我确实得到了不少的性能分,但是进行复杂的优化方法确实对我的代码结构造成了一些负面的影响,可以看出,有不少方法的复杂度比较高,就是因为进行了长度优化。

6. 心得体会与未来方向

通过这一次的作业,我学习到了递归下降法来进行层次化的分析,这种方法刚开始我有一点难以理解,在翻看了指导书、公众号的讲解,研究了相关代码之后,我对它才有了一定的认识。通过这一单元的学习,我再一次对面向对象的SOLID原则和思想有了更加深入的理解,目前感觉我在代码编写的过程中,有些地方不能够完美遵从相关原则,在接下来的学习中我会努力做的更好。

说实话,每周的oo任务量其实也不算小,之前也从来没有接触过互测这种新奇的东西,好在上学期oopre打下了还算坚实的基础。一般情况下,周一晚上指导书发布,我不会立刻去写代码,周一晚上基本都是在构思大体上的框架,思考怎样实现可扩展性更高,代码结构更清晰简明,然后周二周三差不多能把代码写完,周四上午简单做一些测试和修补,中午就可以中测了,然后周五搞搞评测机。每周在oo上确实花费了不少时间。

和去年相比,今年oo的第一单元难度确实降低了不少,在满足要求的同时又不会给同学们太大压力,不过我觉得两次迭代的难度上升幅度可以优化一下,hw2相较于hw1难度上升挺多的,反而hw3相较于hw2几乎没有提升什么实质的难度,我感觉迭代时候的难度提升可以调整一下。

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

301

社区成员

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

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