301
社区成员
发帖
与我相关
我的任务
分享
我的代码主要可以分为以下几部分
我的处理包括两部分,字符串输入的空格和符号处理
字符串处理的部分放在了main函数里面,包括了空格处理和符号处理。其中的符号处理,我认为按照课程组的想法,应该可以在建立表达式树的同时处理多个相连符号,但是为了保证整体的简洁性(防止出现我想不明白的bug),我选择了将相邻的符号合并,将字符串处理成大家最熟悉的样子。
另外,当我刚写的时候以为只涉及到空格处理,应该很短(实际并不是),结果导致后面一起符号处理的时候让main函数里面的方法复杂无比,而实际上应该维持一个简洁的main函数。我认为这方面是我架构的时候就思考不清,应该早点考虑到代码的可扩展性,而不是图省事。
表达式解析主要就是进阿里表达式树的过程。
表达式树的建立。这部分我和课程组提供的思路保持一致,第一次的因子包括表达式Expr类、未知数X类、常数Number类,第二次的因子添加了指数因子Exponent类,第三次添加了导数因子Derive类。在后续的扩展中,考虑到数据过大的时候可能会出现tle问题,我认为在大多数情况下项中的因子都是常数和未知数,因此假如能在向项中添加因子的时候就直接将常数和未知数合并,就会在非全括号的情况下建立叶子节点少的表达式树,从而大大缩小树的深度。而经历了三次强测,貌似也确实没出现tle的情况(说明课程组的数据都给的非常友好)
对于函数的处理,我选择将函数处理成函数树,随后当主计算式中出现函数时,就把函数替换为函数树,将函数中的因子替换到函数树的形参中,实现实参到形参的映射。
我要忏悔!!我要忏悔!!!我要忏悔!!!!我当时小脑瓜想了一下,处理表达式肯定是在表达式类里面吖,然后一拍脑瓜就写在了Expr类里面。。。又由于我为了随时输出查看结果,所有输出也写在Expr类里面。。。直接导致我Expr类的复杂度奇高无比,几乎是其他所有类加起来的和()而且后续维护Expr类的时候几乎完全找不到方法(绷)
正确答案应该是写一个方法类,但当我第二次作业试图改的时候发现已经是一坨了,只能硬着头皮往Expr类里面继续加方法()
拆括号我选用的是递归下降,以及指数函数和导数函数都是在这里面处理的,我选择将两种函数里面的东西先经过递归下降完成最简化之后再求导或其他处理。
以及化简,由于我的处理方法,每次返回的都是最简单的化简过的项,所有直接省去这一步了。
重点介绍一下这个类,这个里面存放的是我的最简式,化简之后里面应当包括系数coff、未知数x的指数conponent、指数函数Exponent。而当化简的时候,肯定会用到比较是否相等,这个时候有两种“相等”方式:① 判断两个Simplify是否真的一模一样 ② 判断是否能同类项合并
对于第一种,我们如果直接判断equals的话基本上都会返回不行(毕竟指向的对象不同),所以需要我们自己重写一个equals函数,让它能够“深比较”。而关于深浅克隆的时期,听说身边很多人经历了赛博闹鬼()我目前还没遇到,但被“深比较”闹了一次,de了很久的bug。

和身边同学交流了一下,我的类很少,按理来说代码应该更短,但最终发现发现我代码算是比较多的……这是由于我诡异的特判优化思路,选用代码复杂度换时间复杂度()时间复杂度低没低不清楚,但代码是真的长()由于之前说过的Expr的超级耦合,导致Expr类几乎一直是贴在500行的边缘游走。
除了Expr类外第二长的是Parser类,Parser由于是解析的功能类,Exponent类用来展开指数函数,面向过程性相对强一些,所以代码规模稍大,至于其它类都是面向对象类,代码规模明显较小。
总体来说,可以很明显的看出,各个类的代码规模与其本身的面向对象/面向过程特性有很强的关联。

之前说过,面向过程的Expr类、Parser类都明显彪红,而被我处理字符串的Main类也由于增加的面向过程而红了……几个面向对象类基本上都是1.00,唯一用的多的Simplify类也只有1.77
所以还是要牢记面向对象思想啊。
第一次作业我写的比较简单啊……
我当时想的很简单,我的主体架构已经包括了递归下降部分、解析表达式部分、数据结构部分和化简部分。当时已经能够通过递归下降处理括号嵌套了,我寻思应该后面就不会涉及到大改动了吧(事实证明我想简单了)
第二次作业我一开始没看到指数函数可以嵌套,想的非常简单,用HashMap来存指数函数,非常快就写完了啊。
结果看到嵌套,直接麻爪了。最后在周五晚上直接重构了,在这次重构里面建立了Simplify类,统一封装最简式里面的三种因子(常数、未知数指数、指数函数)。又因为这次是Simlify类而不是简单的int变量,所以一开始没有想到克隆的时期,又经历了深刻的debug……最终在周六早上通宵完成了重构。
说是重构其实也不算,毕竟主体计算一点没变,但毕竟在所有的方法里面都替换了Simplify类,代码几乎长了一半,所有也可以算重构?
感谢在第二次作业中的重构,所以这次代码写的非常快,只需要写一个求导环节就ok了。但是需要注意求导公式,我最后强测在嵌套求导方面出了问题……最后改了一个小时的公式……
公式都错了居然还过了中测,我自己也是很难绷
我认为经历了第二次作业的重构,我的代码可扩展性很强了。假如要自己假设一个迭代的场景的话,拿上学期的三角函数举例子,我只需要再新建一个关于三角函数的面向对象类,然后把它加入Simplify类的属性里就可以了。
再大胆预测一下,假如下学期是倒数or底数不只是e的幂函数,同样只需要在Simplify类的属性里面假如新的面向对象类就可以了。
先是中测的bug,当时中测只有一个bug,但我自己构造数据测试的时候发现了很多小bug,比如漏项,比如有点地方忘记克隆,比如指数有时候算不对等等情况,改了好几次,最终过了中测。这件事情狠狠给我敲响警钟,中测不可靠!!!一定要自己构造数据!!
结果强测挂在了极其奇怪的原因上,我记得好像是输出模块……我当时很担心我中间拆括号的地方出问题,所以一直在构造考验里面计算过程的数据,没想到居然是输出被刺了我……
说明一个道理,敲代码可以有重点倾向,但测试必须要雨露均沾(bushi)
第二次作业中测很快就过了,但有了上次作业的教训,我依旧做了很多评测,又发现了很多bug,比如0*1^0,由于在新构造的方法里面常数项是被直接计算的,所以当扫描到指数0的时候,目前Simplify里的系数是0,算出来结果就是1。然后为了de这个bug我引入了很多标记,却引入了很多新bug,比如0*(1+2)计算出来是3,为了修新bug又又又引入了新新bug……
教育我们,在写代码之前就要考虑一下特殊性问题,让代码留出一定的余量,否则就是屎山堆屎
然后是强测,我要检讨,我第二次作业和第三次作业都挂在了这个点上:当指数函数的指数是负数的时候不能把它写在指数上面……
我也不很清楚为什么第三次作业也没有改回来,可能隐患是第一次作业就留下来的?
正如上文所说,第三次强测的bug还是出现在负数处理上面,我最后为了改这个负数只好在所有的输出中加特判,结果在bug修复的时候又出现了括号不匹配问题……最后通过添加一大堆特判条件勉强过去了。
其实可以发现,我的绝大部分bug都是同质类bug。我宁可给自己测试七八层exp()、dx()嵌套,都不愿意给自己测试一下简简单单的输出……
再次敲黑板强调,敲代码可以有重点倾向,但测试必须要雨露均沾!!
前两次评测其实我都是瞎评测……拿点基础分就走。但最后一次稍微找到了点窍门,可以先捏一点大数据,包含很多针对边界的测试,比如“零”:0+0、(0)^0。另外就是大边界能够看看对面有没有tle和mle(努力在cost范围内卡边界)
我主要的优化上文有提到一些,针对于稀疏树的优化,在向项中添加因子的时候就直接合并。只要所需要添加的因子中没有表达式,那么就无脑合并,可以大大地减少递归层数,我hack别人的时候也是尽力通过添加递归层数来卡tle
但是优化的缺点就是不能保证简洁性,只能代码换时间,而且给维护带来了很大的困难,比如上文提到的屎山添屎,只能说鱼与熊掌不可得兼,舍代码而取时间者也。
加深了对正则表达式的理解,学习到了如何使用递归下降来解析和生成输入数据。
增强了心理抗压能力。面对每周恨不得精确到分钟的ddl,真的很锻炼心态。
磨炼了意志力,知道了不到最后一刻不放弃,多次OO互测在要放弃的时候用一个新的用例hack成功,只有不放弃,多尝试才有可能不断进步,不断收获新的惊喜和成果。
很有收获!
来商业互吹了,姐姐