第一单元总结

——表达式的求导

一、思路综述

二、代码分析

  结构分析

  bug分析

  风格分析

三、Hack Hack Hack

四、难点总结

五、感想

一、思路综述

第一次作业

输入处理时,一项一项地用正则表达式进行匹配(本来用大正则,结果爆栈QwQ),其中第一项要单独考虑。创建Polynomial(多项式)和Term(项)两个类,Polynomial类中用HashMap保存项,一边添加项一边合并。注意到正项放在第一个要比负项放在第一个的结果短。

第二次作业

仍然用正则表达式的方式处理输入表达式。构建了Expression(表达式)、Term(项),Factor(因子)三类,另外构建了三元组的Struct类,用来存放x因子、sin因子、cos因子的指数。在表达式类中,仍然用HashMap的方式存放项,求导方法调用项类的求导方法。在项类中,以“*”为分隔符分割成多个因子,遍历每一个因子,构造Facotr实例,通过kind属性给相应的因子种类指数加一。

需要注意的是,在三元组的结构体内,要重写方法hashCode和equals,才能继续正确使用HashMap的containsKey和put方法来合并同类项。

对于化简表达式,想秃了也没有找到全面的合并方法,最后考虑了sin(x)^2+cos(x)^2=1,cos(x)^a*sin(x)^4-cos(x)^a*sin(x)^2=sin(x)^2*-cos(x)^a+2,sin(x)^a*cos(x)^4-sin(x)^a*cos(x)^2=cos(x)^2*-sin(x)^a+2三种化简方式。

血的教训是优化前请一定先考虑正确性!!!

public int hashCode() {
int result = 17;
result = result * 37 + this.xindex.hashCode();
result = result * 37 + this.sinindex.hashCode();
result = result * 37 + this.cosindex.hashCode();
return result;
} public boolean equals(Object another) {
return (this.getXindex().compareTo(
((Expression.Struct) another).getXindex()) == 0
&& this.getSinindex().compareTo(
((Expression.Struct) another).getSinindex()) == 0
&& this.getCosindex().compareTo(
((Expression.Struct) another).getCosindex()) == 0);
}

第三次作业

第三次作业进行了很多次重构,起初考虑用正则表达式一项一项地进行匹配,边判断合法性边加入到项的arraylist中。其中嵌套因子的正则表达式被写为"((sin)|(cos))[ \\t]*\\(.+\\)[ \\t]*",然而形如sin(x)+cos(x)的表达式也会被匹配成功,在后续嵌套因子的求导中被判为WF。

因此最后先判断合法性(通过不断匹配"[ \\t]*\\(" + expression + "\\)[ \\t]*",其中expression为第二次作业中的合法表达式,并将其替换为x,直到最后的表达式成为第二次作业中的合法表达式,若不能,则不合法。);

再进行项的匹配,用stack的自增自减来模拟括号的进栈,遇到“(”,stack++,遇到“)”,stack--,当且仅当遇到“+”,“-”时stack==0,且“+-”前不为“*”和“^”时,“+”“-”才能作为加减运算符来对待,也就是说这个时候才是一项的结束;

类似地,在每一项中,要进行因子的匹配,当且仅当遇到“*”时stack==0,才表示一个因子的结束,在项类中用arraylist存放每一个因子;

SinFactor,CosFactor,CoefFactor,ExpFactor,NestFactor类均继承自Factor类,在Facor类中,判断其属于哪一个子类,同时进一步判断其合法性。

以上的所有类都有同一个接口Exprsision,在这个接口中,有共享的正则表达式,和diff方法。每一个Factor子类均有自己的求导方法,Factor类的求导方法是根据它的type属性调用其子类的求导方法。Term类的求导方法即乘积项的求导,ExpFactor的求导方法即加和项的求导,求导方法直接返回字符串。

写得太惨了qwq,根本没能考虑优化。

二、代码分析

2.1结构分析

注:

ev(G):即Essentail Complexity,用来表示一个方法的结构化程度

iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,值越大联系越紧密。

v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。

OCavg:类的方法的平均循环复杂度。

WMC:类的方法的总循环复杂度。

第一次作业

仅包含poly和term两级。

在Polynomial类的print方法中,分别考虑了,只有一个项——printonly,第一个项——printeach,其他的项——printeach,可以看出来printeach的iv和循环复杂度都很高。

第二次作业

三级结构,即Expression-Term-Factor,其中Factor类的复杂度偏低,Expression和Term类复杂度偏高,其中,Expression类的打印和合并同类项的相关方法的iv和循环复杂度明显高于其他方法。

第三次作业

同样可以认为这是三级结构,Exp-Term-Factor,区别只是Exp也有可能为因子。

当Main方法处理完输入字符串的正确性后,若不是表达式因子,就在两侧加上括号,变成表达式因子。ExpFactor类的构造方法中,将传入的字符串分割成两个arraylist,分别用来存放项,以及项与项之间的符号。而Term类中又包含了存放Factor的arraylist,其中的Factor也有可能是ExpFactor类, 所有类都有接口Expression。ExpFactor,SinFactor,CosFactor,CoefFactor和XFactor继承自Factor类。

2.2Bug分析

第一次作业

容易出bug的地方有:大正则爆栈,\s匹配空白符忽略了\v,\f,结果只有一个0时没有输出。

然而只考虑了输出为1项,答案为0的情况,未考虑结果的几项均为0,即0*x^2+0*x^5+0*x^3这样的情况,挨了一刀,还是来自友军的误伤(发出嘤嘤嘤的菜叫)!

第二次作业

好像是在处理项与项连接有三个正负号的时候出现了问题,具体情况记不清了,上oo网站发现bug修复关闭了,看不见了(挠头.gif)。想了好久的优化,结果强测挂了两个点……正确性才是王道!

第三次作业

出现了一些粗心的小bug,很幸运地被强测放过,很惨地被互测刀死了。小bug的问题来源都是忘了考虑空格,比如下面这个情况,当系数或指数中有空格时,可能把正确的项判为WF。

public void checkcoef(String str) {
try {
BigInteger big = new BigInteger(str.trim());
} catch (NumberFormatException e) {
System.out.println("WRONG FORMAT!\n");
System.exit(0);
}
}

2.3风格分析

·正则表达式的部分,采用分级的形式会使代码显得更简洁明了,如:

String factor = "(([+-]?\\d+)|" +
"(((sin[ \\t]*\\([ \\t]*x[ \\t]*\\)[ \\t]*)|" +
"(cos[ \\t]*\\([ \\t]*x[ \\t]*\\)[ \\t]*)|x)[ \\t]*" +
"(\\^[ \\t]*[+-]?\\d+)?))";
String term = "((([+-]?[ \\t]*" + factor +
"[ \\t]*)|([+-][ \\t]*[+-]\\d+))[ \\t]*"
+ "(\\*[ \\t]*" + factor + "[ \\t]*)*)";
String expression = "^[ \\t]*[+-]?[ \\t]*" + term
+ "([ \\t]*[+-][ \\t]*" + term + "[ \\t]*)*$";

·养成了优化代码风格的意识,从全靠Ctrl+Alt+l和checkstyle,到有意识地把代码写得美观一些,控制方法的长度等等。

三、Hack hack hack

首先是拿自己和小伙伴的样例一顿盲狙,这些样例基本上是针对我们觉得自己处理得比较麻烦的地方,比如说第一次的爆栈,结果是0的输出(要命的是我拿这个hack到了别人,结果自己也有问题)。盲狙的过程中,有的时候简短的数据更容易测到bug。

第一次作业的盲狙过后,构造了自认为覆盖性比较好的测试数据,之后的两次样例太复杂了,就省略了这一步。

在此之外,有的时候会看一下具体代码,比如第一次作业,有同学没有考虑表达式结尾部分的正确性;第二次作业,有同学在正则表达式中直接写了"\\d{1,4}",只要超过四位的整数就会出bug;这样有针对性地de一会,也有较大几率能看出问题。然而看别人的代码有的时候是真读不懂,不过第一次互测时,在一段怎么也读不懂作者意图的代码中找到了bug。

四、难点总结

WF

前两次比较容易用正则表达式进行匹配,第三次由于嵌套因子和表达式因子的存在,很难直接判断一个表达式的合法性,故考虑先判断整体结构的合法性,再局部地对每一个项、因子进行合法性的判断,具体实现在前文中有所描述。

项/因子的提取

前两次都是用正则表达式直接一项一项的匹配,在第三次作业中,要对括号进行进出栈处理,栈为空才有可能是一个项或者一个因子的结束,否则是嵌套因子或表达式因子的内部。

优化

第一次和第二次都用HashMap来存储项,第一次的key是指数index,第二次的key是一个三元组的类,表示sin项、cos项、x项的指数。第一次要注意把正项提前,第二次要围绕sin(x)^2+cos(x)^2=1做文章。第三次作业中,由于diff方法的返回值直接是字符串,不方便进行优化,而且为了保险起见,该加括号的地方都加了,0和1也不敢太省,总的来说就得到了一个非常丑陋的求导结果。

实际上,可以考虑做一些简单的优化:

1、输入处理过程中,对由简单因子构成的嵌套因子或表达式因子,进行如作业一、二的合并同类项。比如:sin((x^2+3*x^2+x))可以化简为sin((4*x^2+x))。再进行求导。

2、为每一个因子的求导结果保存一个系数属性,在项的求导中,把系数相乘合并起来。当一个项的系数为0时,后面的因子可以不做考虑。

3、进行括号匹配,当两层括号中没有其他内容时,可以消去一层括号,比如:将cos((((((x))))))化简成cos(x)。

五、感想(Applying Creational Pattern)

三次作业整体的结构(即Expression-Term-Factor)没有变化,但每一次都重构了很多部分。

在第一次作业中没有建立求导方法的层层调用,直接在poly类中进行了求导,在第二次作业中熟悉了HashMap的hashCode、equals方法,更明显地感受到三个类之间的层次关系,在Poly类的求导方法中调用了Term类的求导方法。第三次作业中,进行了大量的重构,包括表达式合法性的判断,抽象出了一个checkstyle方法;设计了Expression接口,清晰了diff方法,也使很多正则表达式得以在几个类之间共享;设计了多个因子的子类,继承Factor类;ExpFactor类和Term类的相互调用;递归性地求导等等。

总的教训是,一定要先把架构想清楚,再写代码,第三次作业反复重构的很大一部分原因都来自于没有考虑清楚层与层之间的关系,子类和父类之间的关系,某一个方法到底是该交给父类还是交给子类,等等。

2019-oo-第一单元总结的更多相关文章

  1. 2019 OO第一单元总结(表达式求导)

    一. 基于度量的程序结构分析 1. 第一次作业 这次作业是我上手的第一个java程序,使用了4个类来实现功能.多项式采用两个arraylist来存,系数和幂指数一一对应. private ArrayL ...

  2. OO第一单元作业总结

    oo第一单元的作业是对多项式的求导.下面就是对三次作业分别进行分析. 第一次作业 分析 第一次作业相对来讲比较简单,甚至不用面向对象的思想都能十分轻松的完成(实际上自己就没有使用),包含的内容只有常数 ...

  3. OO第一单元总结

    OO第一单元作业总结 一.前言 开学四周,不知不觉已经做了三次OO作业.事实上,每一次作业对我来说都是很大的挑战,需要花费大量的时间和精力来学习. 虽然学得很艰苦,但最后还是连滚带爬地完成了.(好惨一 ...

  4. OO第一单元优化博客

    OO第一单元优化博客 第一次作业: 合并同类项+提正系数项+优化系数指数0/1=满分 第二次作业: 初始想法 一开始是想以\(sin(x)​\)和\(cos(x)​\)的指数作为坐标,在图上画出来就可 ...

  5. 【OO学习】OO第一单元作业总结

    OO第一单元作业总结 在第一单元作业中,我们只做了一件事情:求导,对多项式求导,对带三角函数的表达式求导,对有括号嵌套的表达式求导.作业难度依次递增,让我们熟悉面向对象编程方法,开始从面向过程向面向对 ...

  6. OO第一单元(求导)单元总结

    OO第一单元(求导)单元总结 这是我们oo课程的第一个单元,也是意在让我们接触了解掌握oo思想的一个单元,这个单元的作业以求导为主题,从一开始的加减多项式求导再到最后的嵌套多项式求导,难度逐渐提高,编 ...

  7. 【作业1.0】OO第一单元作业总结

    OO第一单元作业已全部完成,为了使这一单元的作业能够收获更多一点,我回忆起我曾经在计算机组成课设中,经常我们会写一些实验报告,经常以此对实验内容反思总结.在我们开始下一单元的作业之前,我在此对OO第一 ...

  8. OO第一单元(前四周)作业总结

    OO第一单元(前四周)作业总结 OO第一单元(前四周)作业总结要求(第四次作业) 0.前言 本次博客针对的是本人学习Java的第一阶段的三次作业的作业总结 第一次作业的内容是:7-1 计算税率 (20 ...

  9. 北航OO第一单元作业总结(1.1~1.3)

    经过了三次作业之后,OO第一单元告一段落,作为一个蒟蒻,我初步了解了面向对象的编程思想,并将所学内容用于实践. 一.第一次作业 1.架构分析 本次作业需要完成的任务为简单多项式导函数的求解.表达式仅支 ...

  10. OO第一单元总结与反思

    OO第一单元总结与反思 目录 OO第一单元总结与反思 摘要 第一次作业 本次作业UML类图 本次作业度量分析 第二次作业 本次作业的UML类图 本次作业的度量分析 第三次作业 本次作业的UML类图: ...

随机推荐

  1. JVM虚拟机

    一.JAVA虚拟机内存模型: 1.程序计数器:非常小的内存,用于存放下一条运行的指令: 每一个线程都必须有一个独立的程序计数器,用于记录下一条要运行的指令,是一块线程私有的内存空间,CPU时间切片 2 ...

  2. 【原创】Linux基础之查看linux发行版以及内核版本

    redhat查看发行版 # cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 查看内核版本 # uname -aLinux $h ...

  3. Mac 解决 Sourcetree 同步代码总需要密码的问题

    git config --global credential.helper osxkeychain

  4. 面试容易问到的Linux问题

    1.有没有使用过linux?用来干什么? Linux是一个长时间运行比较稳定的操作系统,我们一般会拿它作为服务器. 一下软件(redis等)没有软件包,需要在linux的C编译环境下编译得到软件包. ...

  5. VMware虚拟机下安装CentOS6.5

    点击创建虚拟机 选择典型—>下一步 选择安装iso映像文件—>点击浏览选择下载好的centos 镜像文件—>下一步 设置用户和密码—>下一步 设置虚拟机的名字和位置 指定磁盘大 ...

  6. [转] 如何写好.babelrc?Babel的presets和plugins配置解析

    什么是Babel The compiler for writing next generation JavaScript. 官网是这么说的,翻译一下就是下一代JavaScript 语法的编译器. 作为 ...

  7. 利用Graphviz绘制逻辑关系依赖图

    说明:在很多情况下,需要将复杂且有些规律的代码整理成逻辑片段,这个时候就需要画图,很多时候图比代码更加直观 Graphviz是一个比较好的绘图工具,可以通过简单的代码绘制出复杂的逻辑图,且其代码就像平 ...

  8. ubuntu linux adb devices no permissions解决办法

    最近在调试安卓手机时老是失败,问题如下所示 han@ubuntu:~/project/zero_app$ adb devices List of devices attached 664768297c ...

  9. Integer Replacement

    https://leetcode.com/problems/integer-replacement/#/solutions 这题是一道典型的搜索问题,我采用广度搜索,可以直接输出最短路径.这题的tes ...

  10. Scrapy基础(十四)————Scrapy实现知乎模拟登陆

    模拟登陆大体思路见此博文,本篇文章只是将登陆在scrapy中实现而已 之前介绍过通过requests的session 会话模拟登陆:必须是session,涉及到验证码和xsrf的写入cookie验证的 ...