1、作业要求
homework1
本次作业需要实现一个简单的多项式展开,且保证输入合法。由于项中仅有常数项与幂次项,因此每个项都可以化简为 a⋅x^b 的形式,也方便进行同类项的合并、化简与处理。
homework2
新增了指数函数因子、自定义函数因子,允许多层括号嵌套。
homework3
新增求导因子、函数定义时允许引用已定义函数。
2、架构分析
类图与分析
主体流程是存储用户自定义的函数。随后,利用lexer对输入的表达式进行词法分析,紧接着通过parse进行表达式的解析。完成解析后,调用toPoly方法进行运算,并将结果转化为一个统一的形式。最后,这个形式会进一步被化简并输出。
在解析部分,程序采用了递归下降的方法,解析过程分为三个层次:表达式、项和因子。解析表达式时会调用项的解析方法,而项又会调用因子的解析方法。如果遇到特殊的因子,如表达式因子,它还可以再次调用表达式的解析方法,直到整个表达式被完全解析。为了方便统一管理,主要的因子都实现了Factor接口,这样内部属性和特点就可以得到相应的处理。在解析自定义函数调用时,程序采用了字符串递归替换的方法,这一功能的实现主要在myFun类中。
在计算方面,采用了标准项的统一处理方式。建立了Unit类作为HashMap的key,只要key相同,就可以进行合并。表达式类、项类以及每种因子类都实现了toPoly方法,将自身转化为HashMap<Unit,BigInteger>类型,这样就便于进行运算、化简和输出。
此外,程序中的多项式加法、乘法和求导都被写成了静态方法,并放在Poly类中。同样地,预处理和化简的方法也被写成了静态方法,并放在Simplify类中。
3、复杂度分析
可以发现Simplify类的复杂度较高,主要原因是化简需要对各种情况进行单独处理,并含有提取最大公因数和对输入预处理的功能,导致复杂度过高。
4、架构设计体验
homework1
第一次作业主要参考第一次实验的架构完成表达式的解析,同时采取oolens提供的递归下降方法,通过parser将表达式拆解为表达式、项、因子三层,构造出a*x^b的的标准项,表达式遇到加减号调用解析项的方法,将解析完的内容加到项容器中,遇到乘号调用解析因子的方法,将解析完的内容加到因子容器中。最后完成表达式的化简和输出,化简对各种情况进行了特判,将相同的项化简合并并排序输出。
homework2
第二次作业加入了指数函数因子、自定义函数因子,并允许多层括号嵌套。对于自定义函数,采用朴素的通过字符串递归替换得到没有自定义函数的字符串,然后调用lexer、parser等方法去解析,再返回结果的方式。指数函数本质上也是一个因子,因此选用了Factor进行存储。
homework3
第三次作业加入了求导函数,经过预处理后直接求导并返回即可。
5、bug分析
正确性方面没有太大的问题,主要是在边界压力测试时出现TLE情况,由于第二次作业先计算再合并的设计使得面对较大规模计算时程序运行速度难以保证。相应地也使用了类似的压力样例去测试他人的程序。
6、优化策略
对于TLE的情况,改进采用了边计算边合并的策略,提高了计算的效率。同时参考讨论区的分享,注意到了一些虽然不符合标准定义但是能够实现输出字符串更短的方法,例如:
exp((10000+10000*x))=exp((1+x))^10000
exp((2*x))=exp(x)^2
exp((10+20*x+20*x^2))=exp((2+4*x+4*x^2))^5
exp((1+3*x))^2=exp((2+6*x))
exp((3*x+2*x^2+2*x^3+2*x^4+2*x^5))=exp(x)*exp((x+x^2+x^3+x^4+x^5))^2
这一类的输出优化较为复杂和不自然,因此无法去针对实现,只能先作为参考,优先保证正确性。
7、心得体会
本但愿作业是一次迭代开发的过程,将能完成简单功能的程序出发,逐步将其实现为更有扩展性与新功能的程序,并进一步体会面对对象的本质。在本次作业中,我学习到了工厂模式、正则表达式的基础使用、Java 的容器使用、递归下降方法、面对对象的设计方法等,也从第二第三次作业中逐步感受到面对对象设计的好处与可扩展性。
此外,设计架构也是一门大学问。可以说,良好的架构设计应当是具有可扩展性的,也是具有不断迭代修改的工程中需要的。如果架构已经不足以支撑当前的功能,应当及时重新设计架构,推倒重来。同时,设计架构的过程也需要考虑到未来的可扩展性。
8、未来方向
课程总体设计比较合理,能够感到明显的学习和进步。个人的部分建议如下:
个人感觉研讨课的讨论时间不够充裕,常常来不及弄清楚他人的具体想法,可以对展示同学的时间安排做更详细的限制。