301
社区成员
发帖
与我相关
我的任务
分享oo第一单元的主题是表达式展开,以层次化设计训练为主,本单元涉及三次迭代开发。
本篇博客以介绍三次作业迭代分析的思路和架构为主,包括各方面细节的实现思路和bug分析。

架构设计上主要包括两个层面:表达式解析(递归下降)、表达式展开
整体过程便是先解析表达式,然后对于每一个因子都转化为多项式的形式进行合并和计算,最后输出
由于题目中表达式第一项、项的第一项会有符号,数字因子本身也可能带有符号,因此在处理语法单元时先采取了replaceAll进行了预处理

接下来则是对表达式解析,在通过预处理之后解析的过程几乎和训练题一样了,在Lexer中通过next方法顺序访问字符串,根据读取到的字符来分析语法单元
Parser的设计共有三个方法:parseExpr,parseTerm,parseFactor
对于解析表达式和项的方法,大致过程类似,首先对第一项的符号做判断,然后解析第一项(因子),然后通过while循环依次对后面进行解析
parseFactor:共有三种情况:表达式因子、数字因子和变量因子
数字因子最容易解析,只需要判断当前语法单元是否为数字,并将当前语法单元实例化为数字因子类的对象,返回该因子即可(数字因子类存储数值和符号)
解析变量因子时,需要判断之后是否存在”^”符号,如果有的话继续访问接下来的语法单元,将指数获取,并返回实例化的变量因子对象(变量因子类存储指数和符号)
解析表达式因子时,以左括号的语法单元判断作为标准,继续递归处理里面的表达式,再重新返回回来时判断是否存在”^”符号,这里由于没有建立表达式因子类,选择直接将乘方展开并化简,返回整体的表达式,后续在介绍输出和化简部分时讲述如何展开乘方
首先分析输出的结果,对于表达式的恒等变形,最终一定是一个多项式的形式
因此引入两个类:Poly类(多项式类)、Mono类(单项式类)
Mono类含有两个成员变量,系数coe和指数exp
Poly类含有ArrayList容器,储存一系列Mono类的对象,并编写多项式加法、多项式乘法的方法
多项式加法比较简单,就是直接遍历两个Poly类的容器,访问他们的每一个单项式
这里合并同类项采取HashMap容器,Key值存指数,Value存系数,
因此在访问Poly类的容器时通过判断HashMap是否存在相同Key值便可做到合并同类项和加入新的项
多项式乘法同理,两层循环遍历乘法,系数相乘,次数相加,同样通过HashMap进行合并同类项
接下来我们需要将Expr类、Term类、还有各种因子类里面写一个方法toPoly(),将该类的对象转化为多项式的形式
对于数字因子类和变量因子类,系数和指数都是显然的
项类转化为多项式(单项式),只需要将ArrayList中存储的因子所转化成的多项式形式再调用Poly类的相乘方法即可
表达式转化同样只需将存储的Term转化成的多项式调用Poly类的相加方法即可
通过这样自下而上递归便能得到最后结果所需要的数据
前文中提到的在parseFactor中展开表达式的乘方,便是将表达式转换为多项式形式(调用toPoly方法),在Expr类再添加一个乘法方法,通过for循环不断调用Poly类的多项式乘法相乘即可

对于toString中的输出形式也可以稍作化简


第一次作业中Poly类的toString方法分支判断过高,调用方法数也较多,这也为第三次作业的CTLE埋下了伏笔(后续细说)
本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用的表达式,输出恒等变形展开所有括号后的表达式。

新建了Definer类用来定义和替换自定义函数
表达式结构中Factor接口再接入ExpFactor(指数函数因子)、ExprFactor(表达式因子,由于第二次作业中提前处理乘方无法处理自定义函数,故而选择新开表达式因子类方便处理)、FuncFactor(自定义函数因子)
采用的思路是先预处理自定义函数,在Definer类里用两个容器存储,一个存储函数名和对应的函数表达式字符串,另一个存储函数名和对应的形参集合
在解析到自定义函数名时,先解析出函数对应的实参,再将实参传入Definer类中的字符串替换方法,将实参替换形参,然后返回整体的表达式字符串,再对该字符串进行解析
部分代码如下:

注意:要规避exp中替换x的方法,再遍历到e的时候要进行特判
在第二次作业中,最小语法单元从系数乘以幂函数的形式,变成了系数、幂函数和指数函数的相乘,因此在第一次作业中采取HashMap中的Key存幂函数次数,value存系数的方法就需要重构
这里我们改用TreeMap存储,由于TreeMap中自带红黑树的顺序,因此采用Key存储字符串(字符串为幂函数和指数函数相乘的字符串形式),这样由于解析方式相同并且转换为字符串都为固定顺序,因此合并同类项便有了正确性保证,再用Value存储Mono类的单项式,这样就在重构的基础上最小的改动了第一次作业的运算方法。
部分代码如下:

注意:为了保证指数函数合并同类项的正确性,在解析完之后将指数函数的指数乘入括号内,比如对于exp(x)^2,处理成exp((2*x))


强测出现bug:是一个连续多个嵌套的表达式因子乘方展开,于是采取对于只有一项的表达式因子,通过直接计算的形式减少时间复杂度
互测出现bug:CPU_TIME_LIMITED_EXCEED,最初因为本地瞬间就能跑出正确结果而疑惑超时原因,于是通过尽可能的减少方法调用强行优化通过,但依旧为第三次作业的bug埋下伏笔
本次作业新添了求导功能(但自定义函数表达式不包括该因子)
自定义函数之间可以互相调用

和第二次作业相比变化不大,自定义函数互相调用在递归下降算法中完全不用改动就可自行解决
只需新加求导因子的解析和新建类,并在每个因子下新加求导方法
部分代码如下(此处为Term类求导):




第一周突然就上强度,从假期的状态脱离很困难,即使有oopre的基础第一周依然很艰难,不断的讨论和思考才研究好了自己的架构
第二周对于新的表达式展开和计算方式也是想了好久,实现完也是错漏百出,过了中测但自测一堆bug
第三周最为轻松,但是改互测bug的时候很痛苦,把bug改完才撰写这个博客,压着博客提交的ddl很难受,CTLE的bug确实难改,以后在写方法时还是要多注意耦合程度和调用过多的问题,尽量的去简化
每周oo给的压力都好大,思考的煎熬,写代码的折磨和debug的痛苦,直到周末好不容易喘气又进了互测,希望接下来能坚持下去,更加从容地对待这门课程