301
社区成员
发帖
与我相关
我的任务
分享代码规模统计:


Factor为因子接口,其包含:
Num(数字类),含有数字的值以及正负号;
Letter(字母类),含有变量x的次数;
Exp:e的幂类,含有e的指数(Poly);
Function:函数因子类,含有函数名以及变量;
De:导数因子类,含有dx中包含的表达式;
Expr:表达式因子类。
Term:包含ArrayList factors;
Expr:包含ArrayList terms;
以上三层层层递进。
此外还有:
FunctionOrigin:用于存放输入的函数的名称、变量、表达式等性质;
Parser:用递归下降法解析表达式。
Lexer:记录当前表达式读取的字符(串),在Parser中使用。
Poly:多项式类,针对前文提到的三层类,各自都有转化为Poly的方法。
Index:包含于Poly的TreeMap中,记录着x的次数以及e的指数。
Main:主函数类,通过读取,解析,计算,输出这四个步骤得出结果。
在第一次作业中,我依据题目中的要求进行了不同类的设计。由下而上主要分为三层。最底层的Num类、Letter类分别代表数字与x的幂,此外还有Expr类,它们统称为因子(接口Factor);它的上层为Term类,即多个因子相乘得到的项;最上层为Expr类,即表达式类。值得注意的是,我没有单独设计表达式因子类,我将它归属于表达式类。
类的设计便是如此了。在整个运行过程中,我分为三个步骤:解析、计算、输出。在解析这一步,我采用了递归下降法。我将读入的字符串当做一个表达式开始解析。在解析一个表达式时,我再去解析其中的每个项,依次加入至Expr的数组terms中。针对每个项,再去解析它的每个因子,依次加入至Term的数组factors中。由此,对一个表达式的解析便可以水到渠成。
其次就是计算。针对计算,我也采用了一种递归的方法。我引入了一个新的类Poly,即多项式类,这里面有一个HashMap<次数,系数>,记录着不同的x幂的系数各是多少。在整个计算过程中,从最底层的因子开始,我使用了toPoly的方法,将其转化为Poly类;到了Term类之后,我还是使用的toPoly的方法,运用多项式乘法将其转化为Poly;到最后的表达式类时,我运用多项式的加减法将其转化为Poly。由此,便可以使整个算式转化为多项式类。
最后的输出是比较简单的一环,需要做的就是把多项式类利用toString的方法转化为一个字符串。具体的实现就不再多提了。
hw1的类复杂度如下:

方法复杂度如下:

第二次作业主要新增了两个任务:一是要处理引入的函数,二是要处理exp()。
针对第一个任务,我新增了两个类:FunctionOrigin和Function。前者主要用于存放输入的函数的名称、变量、表达式等性质,后者也是一个新的因子——函数因子。在解析过程中,同其他因子一样,表达式中的函数部分也被当做因子解析到了Term的factors里面。而在计算(转化为Poly)的过程中,我开始进行函数因子中变量的替换,按照自变量的顺序依次将表达式中的字母替换为对应的因子,然后再将替换后的字符串经解析、计算转化为Poly类。
针对第二个任务,我首先创建了Exp类,其中有bottom属性,即e指数。为了便于计算,我重新改变了Poly类的HashMap的键值对为<Index,BigInteger>,其中BigInteger为系数,而Index也是新增的类,其中包含了x的指数、e的指数(此为Poly类)这两个属性。针对这个改变,Poly类中的加减法、乘法也有相应的微调。以此为基础,计算便可以迎刃而解了。
本次作业新增了两个任务:一是输入的函数可以调用上面的函数,二是引入了导函数。
针对第一个任务,我并未做处理。实际上,在我进行Function类的替换之后,对新字符串的解析、计算,已经保证了其中调用的其他函数被当做因子处理过了,所以最终得到的Poly仍然是正确的。
针对第二个任务:我新增了De类,即导数因子类。同toPoly方法一样,因子、项、表达式都有一个toDe方法,在De类中,通过对dx中的表达式toDe便可得到相应的导函数。
由于hw2,3差别不是很大,故只展示hw3的复杂度分析。
hw3的类复杂度如下:

方法复杂度如下:

将两次复杂度对比可以看出:由于架构的设计比较顺利,所以从第一次作业到第三次作业虽然规模扩大,但总体复杂度变化不大。
假设新增的任务是增加三角函数(sin,cos)的有关计算,以我现在的程序来看,我可以增加三角函数类,属性有函数名,函数自身变量等因素,在对Poly的设计中,要在Index中添加三角函数的次数这个属性。计算、输出等加之修改即可。
在第一次作业中,我完成得相对轻松,在强测与互测中均未发现bug。
在第二次作业中,难度相较于第一次提升较大,在完成作业之后,我仍发现了不少的bug。首先就是在Lexer类的编写中,容易忽略Lexer.next(),但是这个错误相对明显,直接通过样例还是比较容易找出的。我最令人啼笑皆非的错误就是在我处理输入的函数时,竟然忘记了处理空格…这直接导致我第二次作业的强测分数受到严重的影响。经过分析以后,还是源于以及测试不充分导致的,在编写数据时,应该考虑到有空格的数据。
在第三次作业完成过程中,受到第二次作业强测得分较低的影响,我在这次作业上做的比较细致。整个下来也还是发现了bug。由于我在每个因子中都编写了toDe,即求导的方法,在Expr的求导过程中,理论上是用次数乘以底数的(次数-1)次方,而我一开始则写成了次数乘以自身的(次数-1)次方,这就导致了错误。
总而言之,我认为最多的bug还是发生在将不同类转化为Poly的方法中。我认为还是应该针对不同类的toPoly设计简单的样例专门测试,这样的话能够较快地定位bug问题所在。
我的hack策略比较直接。在第一次作业中,我较为系统地尝试了诸多可能的输入,比如表达式的幂次、多个不同因子相乘、多个正负号、一些特例(比如x^0)等等,通过举出多个例子直接去hack他人,但终究是无功而返…
在第三次作业中(第二次作业由于强测得分较低未能进入互测ToT),我仍然采用了第一次作业中的hack策略,针对不同的类型,譬如多层函数调用、复杂的函数求导、多重求导、对多个因子相乘求导、对表达式的幂以及exp的幂求导、一些特例等等,设计了不同的输入,最后还是无功而返(哭)。
上面的方法想着可能有效,但还是不能做到全覆盖,测出他人bug的几率也就大打折扣了。其实最有效的方法就是自己编写数据生成器,不断对他人的代码评测,这样会大大增加hack成功的几率。鄙人能力不足,所以只能手动构造样例了。
第一次作业的完成较为顺利,在程序优化方面,需要注意的就是性能。我在与同学讨论之后才醒悟,第一项如果是负的,可以先输出正的项,这样可以节省一个字符。
第二次作业的优化要求相较于第一次作业就多了不少。特别是在输出字符串处理这一块,如何缩短输出成了一个比较令人头疼的问题。其一是减少exp的括号,方法就是判断exp中是否是表达式因子,如果不是就可以去掉一层括号。其二就是对于exp中指数的处理,能否通过提公因数至exp的幂次中来减少长度。很可惜的是,我在本次作业的大部分时间都花在了找bug上面,在性能这方面丢失了不少。
第三次作业是我在本单元作业中优化程度最高的一次作业。由于本次作业完成较快,我走了更多时间去找寻可以优化的地方。首先就是运行速度问题,在评测过程中,我发现对于表达式的较大幂次的输入,我的程序运行相当慢。在经过不断地分析与调试之后,我做出了以下几个修改。一是在Poly类中加上了一个新的属性set,用于记录这个Poly的toString。这样的话,在比较不同多项式是否相同的过程中,就不用多次调用toString方法了,而是直接调用getSet方法即可,这样就可以节省大量时间。我们只需要在Poly发生变化的时候相应地进行set的改变即可。二是在多项式加减法与乘法的方法中,我对于map的遍历方式原本是用Map.Entry< , >的方法进行遍历的,后来我改成了仅对key进行遍历的方法,这样就可以大幅度加快程序的运行速度。
回顾这三周的历程,我感受颇丰。尽管上学期学习了oopre,但在本单元作业开始的时候,我深感力不从心。对于递归下降法,我理解了比较长的一段时间才开始着手去写代码。但随着作业的推进,我对表达式处理的理解也越来越深。架构的重要性是最令我感慨的,有一个清晰的架构真的十分重要。实际上,对我而言,第一次作业是最难的,因为它是从零开始,去完成一件崭新的事情。当然,第二次作业的难度显然大得多。尤其是在对于函数处理上,由于我刚开始写的时候没有将它当作一个因子处理,导致我在写完一段代码后不得不删掉重写。这个经历也让我认识到了层次的重要性。此外,在整个过程中,我的Debug能力也有了提升,对于递归的理解也大大加深,这对于今后将大有裨益。
我觉得咱们北航的oo课程设计的还是非常好的,唯一的小期待就是在互测中的数据限制希望能放宽一点。