第一次作业
结构
OO第一次作业是简单的多项式求导,在这次多项式求导中,仅包含了幂函数和带符号整数的一些简单求导运算。在经过自己的一些简单分析后,我认为第一次作业最重要的部分是判断表达式的合法性和求导运算过程。
关于判断表达式的合法性,我采用的方法是先考虑带符号整数内是否存在空白字符的问题,主要对带符号整数数字内部以及符号和数字之间是否有空白字符;在解决空白符问题后,我将空白符全部去掉,然后进行判断表达式格式是否正确的操作,首先我用了一个正则表达式来判断整个表达式:
"((\+|-){1,2}((\d+(\*x)?)|(x(\^(\+|-)?\d+)?)|(\d+\*x\^(\+|-)?\d+)))+"
在进行对表达式的整体判断后,我再通过正则表达式将每一项都分隔开来,用Matcher类的find方法来实现:
"(\+|-){1,2}((x(\^(\+|-)?\d+)?)|(\d+\*x\^(\+|-)?\d+)|(\d+(\*x)?)){1}"
将所有分隔出来的项进行符号化简后存入全局数组。
接下来是对每一项的求导问题。在对每一项求导之前,我先判断每一项的类别,对于常数项,求导为0,不予考虑;对于简单的一次方x项,求导为其系数,通过split将系数分隔出来并存入全局ArrayList cons中,对于其他幂函数类型,求导操作为先用spilt将系数和指数分隔出来,求导运算后将求导后的表达式系数放入全局ArrayList coef中,将指数存入全局ArrayList index中。
在求导运算结束后,再将cons,coef,index组合成求导运算结果。
经验与bug分析
第一次OO作业是第一次比较正规的java程序作业,对本人来说,从未接触过java和面向对象,所以准备有些不够充分,由于并未能很好地理解面向对象的意义,所以作业的写法还是面向过程的写法。
在互测过程中,由于未能解决空白字符中只包含空格和' '这一点,导致被HACK了几下,不过这一点的改正方法比较简单;更严重的一个bug便是正则表达式过长导致爆栈,之前由于未能很好的认识正则表达式的运作机理,所以把整个表达式的判断全堆在了一个正则表达式中,实际上,更为合理的做法是将每一项分隔出来后再通过正则表达式来判断格式正确与否。
第二次作业
结构
第二次OO作业新增了sin(x)和cos(x)三角函数的求导,并且每一项可以包含多个因子。初次分析后,我发现这次作业可以将每一项表达式化为固定模式,即常数因子,幂函数因子,三角函数因子组成的项,由此可以得到统一的求导方式后化简,所以重点还是对表达式的判断和项的求导运算。
在表达式格式的判断方面,基本还是沿袭了第一次作业的想法,判断空白字符的问题,不过这一次要注意sin,cos内部也不能有空白字符的存在,在判断空白字符后,我除了将空白字符去掉,还进行了一次化简,为了后续求导的方面,我将幂函数和三角函数中未显示出的一次方加上。接着依旧是对表达式的判断和分离项,我分别用了两个正则表达式来进行这两步的操作:
"([\+-]{1,2}(([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+))(\*(([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+)))*)+"
"([\+-]{1,2}(([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+))(\*(([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+)))*)"
在对每一项分离后,开始求导运算。首先对每一项进行判断,主要分为常数项,幂函数项,sin(x)三角函数项,cos(x)三角函数项,以及复杂的多因子项,判断这些类别用了五个正则表达式:
"([\+-]\d+)" //常数项
"([\+-]x\^[\+-]?\d+)" //幂函数项
"([\+-]sin\(x\)\^[\+-]?\d+)" //sin(x)三角函数项
"([\+-]cos\(x\)\^[\+-]?\d+)" //cos(x)三角函数项
"([\+-](([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+))(\*(([\+-]?\d+)|(x\^[\+-]?\d+)|(((sin)|(cos))\(x\)\^[\+-]?\d+)))+)" //复杂项
针对这五种不同的项,做出不同的求导运算。常数项不作处理,对于幂函数项,和第一次作业的处理相同,使用两个ArrayList分别保存求导后的系数和指数,对于两类三角函数项,借鉴幂函数的处理方法,将其求导后的指数和系数放入特定的ArrayList中。最为复杂的是处理多因子项了,先用split将每一项分隔出来,同样将没个因子分为四类:常数因子,幂函数因子,sin(x)三角函数因子,cos(x)三角函数因子。对于每一项因子,都可以分为上述的四类因子,分别将合并后的因子求导后再与其他因子的乘积相加,得到复杂多因子的求导表达式。
最后,将各类项组合起来,就得到了求导后的表达式了。在输出之前,我进行了简单的化简,即将一些结果为0的项删除,以及一些多余的符号化简。
经验与bug分析
这次作业相比于第一次作业,需要考虑表达式的一些格式更多了,求导运算也增加了多因子求导,不过通过一些对表达式格式的分析,可以系统化的对每一种格式的表达式进行求导。在写本次作业之前,本人的打算是先仿照第一次作业构建基本的框架,在能够实现相关功能后能够去尝试用面向对象的方法构造整个框架,不过迫于时间原因和bug修复,未能很好的执行。
在互测过程中,由于一些小地方的写法没注意,导致有一些情况未考虑完善,所以一些测试点遭到了HACK,不过这些bug都是由于在写框架的一些小细节中未注意到的错误,所以改正还是算比较简单,在这里就需要以后对这些字符串的处理要细心严谨。
第三次作业
结构
第三次OO作业相比于前两次作业,可以说难度加大了很多,在第二次作业要求的基础上,增加了表达式因子和嵌套因子,这样一来,是的表达式的项求导更加复杂了。在第一次分析后,我尝试用正则递归来判断表达式的合法性,但是结果显然失败了,正则表达式并不支持递归判断。因此,我尝试将每个项分离出来,再将每个因子分离出来,在分离的过程中,判断和求导同时进行,同时支持对于表达式因子和嵌套因子的递归操作。
在分离过程前,我先对整个表达式进行括号是否匹配问题的判断,然后同样对空白符进行判断合法性,然后对将要处理的表达式进行一些化简操作,包括删去空白符,补上指数1等操作,接下来就是进入表达式处理的方法中了。
在表达式处理的方法中,我先将每一个项提取出来,在提取的过程中,最重要的就是关于括号和+,-问题,为此我设置一个两个计数器,分别对左括号和右括号计数,来判断提取项的中断条件,对于+,-问题,我用特判来避免在项中的+,-出现中断现象。对于每一个分离出来的项,我们将它传入处理项的方法中,同时返回来的是该项求导后的表达式,若格式错误,则返回字符串"false",由此,我通过方法返回求导后的表达式这种方法,来完成判断合法性和求导同时完成的操作。在表达式处理的方法最后,我将各个求导后的项连接起来,将最后得到的求导后的表达式返回。
在项的处理方法中,效仿表达式的处理方法,将每个因子分离出来,在分离因子的过程中,也要注意括号匹配的问题。对每个分离出来的因子,我将它传入处理因子的方法中,同样返回得到因子求导后的式子,不合法的话依然得到"false"。同样的,剩下的操作类似第二次作业,将每个因子求导后的式子与其他因子相乘,最后将所有结果连接起来,这样的得到的项求导后的式子返回。
在对因子的处理方法中,首先判断每个因子是何种类型。在这里,我依然将每种因子划分成了几种类型,包括常数项,幂函数,三角函数,分别用正则表达式判断:
"[\+-]?\d+" //常数因子
"[\+-]?x\^[\+-]?\d+" //幂函数因子
"[\+-]?sin\(x\^1\)\^[\+-]?\d+" //sin(x)三角函数因子
"[\+-]?cos\(x\^1\)\^[\+-]?\d+" //cos(x)三角函数因子
对于这几种因子,求导也较为简单,在此不再赘述。除开上面的集中因子,这次还有两种因子的求导较为棘手,那就表达式因子和三角函数因子。实际上对于表达式因子,我们只需要将其再调用表达式的处理方法,得到求导后的表达式即可,对于三角函数嵌套问题,我们也只用结合三角函数的求导操作,再将嵌套里的式子再调用处理因子的方法中即可。
在处理完整个表达式后,再在输出之前将式子简单的化简一下即可。
经验和bug分析
第三次作业无疑是目前最有挑战性的一次作业,虽然差点就要迎来第一次无效作业,但是在这次作业中确实学到了一些东西,积累了一些经验,也认识到了自己的不足和今后的课程学习方法。因为表达式求导运算的复杂性越来越高,也意味着要求我们对程序的设计框架等,多费心思,为了实现作业的功能要求,我们也需要不断地要求自己学着去用更好的方法,构架来实现需求。
在这次的强测和bug互测中,除了日常的一些未曾想到的情况被HACK,最主要的这次还是关于架构的设计问题,面对嵌套过多的情况,就会出现CPU运行超时的bug,这也是自己设计的很大不足,也是自己对面向对象设计的理解偏差问题。
总结性分析
结束了这一阶段的OO学习,虽然每一次作业能够通过弱测,中测,但是在强测和互测的过程中并没有做的很好。我分析了一下这一原因,一方面是自己对自己的要求还不够高,在完成任务后就没有扩展提高的意识,今后要向大佬同学们学习其经验,多看看别人的代码,参考别人考虑问题的思维方式。另一方面,是自己对面向对象的理解问题,在这么多次作业中,自己并未能很好的运用面向对象的知识,虽然通过理论课程和一些相关资料已经有了一定的认识,但是显然缺乏练习,实践,就无法理解通透面向对象的精髓所在,也导致自己的程序架构变得很难看,也很不准确,这都是在今后的学习中需要强加练习的地方。
同时,主要分析一下第三次作业的程序结构:
其实看得出来逻辑不算很清晰,至少给别人看的时候,类也比较少,更多的还是像面向过程式的编程方法。其实自己认为如果能更熟练的掌握的面向对象的各种写法,应该会在此基础上写得更有逻辑一点,最终的效果也应该会更好。在后面的作业中,自己应该要尝试更加熟练的写出OO风格的代码,理解继承,接口等相关知识,设计好自己的框架,理清自己的逻辑思路。
互测中的HACK策略
在这几次的互测中,我觉得自己找bug的策略主要是制造一些可能产生bug的刁钻数据点,阅读别人的代码,发现他们潜在的一些bug,再针对这一方面去下功夫。
Applying Creational Pattern
如果对自己的代码进行重构的话,我想我应该着重会解决类的问题,由于之前几次作业并未很好的展现面向对象的精髓所在,所以在建立类和方法这一方面,应该会有较多的改变。例如,对一些项,或者因子,如三角函数,幂函数,常数,这些有着类似处理办法,我便可以定义一个接口来创建这些共性的方法行为,给各类项(因子)创建一个类,去实现一个接口可以完成求导运算。