OO第一单元总结

代码分析

第一次作业

UML图:

 

 

度量分析:

 

  第一次作业比较简单,整体来看,代码复杂度比较低。

  输入 由于第一次作业保证输入的合法性,先对字符串进行预处理,将空白字符去除,然后将++,- -,+ -,- +转变为单一正号或负号。

                                

   我使用了如上正则表达式使用Matcher的find()方法去匹配每一项,用捕获组来获取每一项的系数和次数,(注意要处理系数和次数的特殊情况:系数省略和次数省略)。用Hashmap来存储次数和系数,以次数作为key,次数为value,对于相同的次数进行合并,若系数为0,则从Hashmap中删除。

  数据存储和管理 输入完成后,用Hashmap中的键值对创建对象,其中Polyentry中保存了多项式中项的系数和次数,Poly中用容器来管理Polyentry。

  输出与化简 第一次作业没有太多需要化简的地方,唯一能做的就是将正项先输出,省略正号。

 

第二次作业

UML图:

度量分析:

 

化简三角函数类复杂度: 

表达式类的复杂度:

 

  第二次作业较为复杂。输入延续第一次作业,在因子中新加入了sin(x)和cos(x),然后对整个字符串进行匹配,若匹配错误,则输出Wrong Format。

                             

  数据存储与管理 每一项设计为系数 + 幂函数 + 正弦函数 + 余弦函数,在表达式中用容器来管理项。于是每一项求导时可以按照固定的模式进行(对每种类型因子依次求导后与其他因子相乘),但这种方法不易于下一次作业的扩展。不过为了化简时的方便,我还是采用了这种方法。

  输出与化简 对于化简,曾经尝试写了两种方法,第一种效果不太好,第二种方法是,将幂函数相同的项放在一个容器中,在其中找到有1,sin(x)**2和cos(x)**2这种关系的项进行化简,所有具有这种关系的项可以构成一棵树,树的结点的值表示为系数,一个结点的子结点看成是这个结点乘sin(x)**2和cos(x)**2。化简要求为长度尽量短的表达式,相当于树上值为0的结点最多。所做的变换是,可以通过在父节点上加一个值,同时在子节点上减去同样的值。但我没有想到在规定时间内能化简树的方法,我最后只进行了局部的化简,找到可化简的部分,将其设想为一棵深度为2的二叉树,除去公因式,进行根节点为1,叶结点为sin(x)**2,cos(x)**2的化简,再重复进行多次这样的化简,可以得到较好的结果。由于题中给定表达式的最大长度,三角函数的项不会太多,如果能写出更深层次的化简,可以化简到不错的程度,由于时间不够,我也没有继续进行,化简相关的类写了200多行。

  复杂度分析 复杂度主要集中在化简类和表达式类中,化简类中有较为复杂的操作,表达式类的求导中需要调用项的求导方法,还需要调用化简类中的化简方法。

第三次作业

UML图:

 度量分析:

 

 

 

 

  复杂度主要集中在Generate和Identity类中,类的内容在后面分析

  第三次作业加入了表达式因子和嵌套因子(作为三角函数的因子),难度有所提升。

  便于对表达式因子和嵌套因子的处理,定义了Factor接口,Sinx,Cosx,Poly,Cst,Expression四个类实现了Factor接口,各自都实现了求导方法。

  输入 由于加入了表达式因子和嵌套因子,sin(((......)))难以直接用正则表达式去匹配。Identity类进行输入处理

  预处理:判断第一个非空白字符是否是符号,若不是符号,则在该位置加入“+”,使得每个项前面都有符号,可以进行一致的读取。

  读取表达式时,按加减号分成每一个项,要注意加减号不能被任何一个括号包含。读取项时,按乘号分成每一个因子,然后调用对因子的生成方法,直到读到文件末尾或者下一个加减号,结束对这一项的读取。

  数据存储与管理 Expression类中以项为单位存储和管理。在输入阶段,得到因子的字符串后,调用Generate中的FactorParse方法,根据得到的字符串中第一个非空白字符,使用相应的正则表达式对字符串进行解析,选择调用四个类中的方法去构造Factor,遇到表达式和嵌套因子则进行递归构造,遇到不匹配的情况抛出异常。

  在Term类中以系数,幂函数,正弦函数列表,余弦函数列表来进行管理,输入处理的过程中还要进行同类因子的合并。

  求导 对幂函数,三角函数列表内的每一个项分别求导,乘整个项内除去这一项的所有项,得到项的数组,与解字符串时类似,遇到表达式因子时,需要递归调用表达式的求导方法。

  输出与化简 出于第三次作业最终表达式可能较为复杂的情况,只进行了基本程度的化简。做到基础的化简,第三次作业性能分便可以满了。

  第一种情况是表达式因子中只有一项,并且该项可以独立作为因子,在解析因子时便可以将该项作为因子返回。

  第二种情况是表达式(因子)中又包含表达式因子且该因子只有一项,在解析项时直接将该项返回。

Bug总结

第一次作业: 强测只得到了80分,看到错误点自己懵了,原来我把x**2输出为x*x,不符合第一次作业的输出要求,让我深刻的认识到每次作业前仔细看指导书是多么的重要。

第二次作业:被别人找出了4个bug,通过与正确求导结果的对比,发现自己if-else中漏判了一种情况,导致了这种错误,最终修改一行通过了bug修复

第三次作业:也被别人找出了4个bug,其中有三个是同质bug,同样也是漏判了一种情况,导致化简输出出现错误,但不知道为什么第四个bug在bug修复时不见了,那个样例找出了我的一个致命错误,当表达式因子中只有一个项且为0的时候我没有输出,不过我还是作了修复。 

虽然通过了强测和bug修复,程序中可能还会存在bug,为了减少bug的发生,在设计可能需要多动一些脑筋,怎样使模块间的耦合度降低,写出层次关系容易理解的代码,同时降低了debug时的难度。

 

找别人bug

第一次作业:我用了自己曾经错过的一些样例去测试,发现有两个人和我有同样的错误。

第二次作业:我自己写了评测程序,利用python的科学计算包,进行正确结果的比对,随机产生的样例,对其他几个人的代码进行黑盒测试,均未发现bug,等到互测情况公开后,房间内只有我和另一个人有bug,而且另一个人承担了房间内80%以上的攻击,我当时用idea无法运行那个人的程序,要是坚持下去,或许还能找回一些分数。

第三次作业:我同样使用评测程序尝试攻击,但根本没有收获,使用自己设计的样例也没有攻击成功,等结果公开后,还是和第二次作业一样的结果。。。后悔自己没有坚持下去。

对象创建模式

就第三次作业来说,我使用了简单工厂模式,Factor作为接口。Cst,Poly,Sinx,Cosx,Expression实现了Factor,调用工厂里的方法返回一个Factor。在解析因子时,根据字符串的第一个字符的不同,调用不同的创建方法来创建具体的因子。在创建方法内部,使用正则表达式解析出各个组成部分。

以sinx类为例,使用

    private static String num = "([+-]?\\d+)";
    private static String expnt = "\\*\\*[ \\t]*" + num;
    static final String sinx = "sin[ \\t]*\\((.+)\\)(?:[ \\t]*" + expnt + ")?";

 

来捕获sin()的因子部分和指数部分,对于因子再次调用工厂解析,递归下去直到得到不可再解析的因子为止。

对比和心得体会

  第三次作业构思的过程中,我也考虑过使用树,在每个结点上加一个标记,表示它是叶结点,还是与子节点之间具有加、乘、复合的关系,每个结点用基本的因子表示,构成一棵树。这样来做,求导会方便很多,只要定义了每种关系下的求导法则还有各种基本因子的求导法则,只要通过对树的后序遍历即可得出结果,这样可以避免我使用的方法递归层次过多导致爆栈的情况。但这种情况下进行化简和合并同类项需要做出更多的努力,变成树的相关问题,与我们日常的求导思维不同。为了与日常思维贴切,我还是采用了化简和合并比较简单的以容器来存储每一项,项中有序地存储各种因子。这样,在设计上比较容易实现,不需要考虑用树存储的相关问题,但可扩展性较差。鱼和熊掌不可兼得,按照作业的要求,我还是选择比较适合自己思维的设计。

  通过第一单元的学习和设计,对面向对象程序设计感觉有点意思,与C语言相比,我不需要去考虑内存空间的回收,我只需要思考按照程序的需求,需要什么样的对象,类之间的层次关系,对象之间如何去交互,比较贴近我们的日常思维。利用接口和抽象类,我们可以以抽象的角度去构思类之间的关系。还有工厂方法,面对多个类,建立一个工厂统一创建所需的类,调用者无需关心创建的细节,专心考虑对象的工作和交互即可。

  OO的学习只是刚刚开始,占据了每周的大多数时间,每当bug修复结束,新的作业又开启,我们只能不停地向上攀登。

posted @ 2020-03-19 00:40  困于街头  阅读(178)  评论(0编辑  收藏  举报