前三次OO作业总结

一、作业总结

前三次的任务都是表达式求导。这是我在高中就思考过的问题,但是很久都没有付诸实践,直到学习了“类”这个强大的工具。还有正则表达式,如果能适当使用,则不失为一个字符串格式检查的利器。真觉得有点编译原理的词法分析的赶脚。

从结果来看,不甚满意,尤其是第二次作业,由于一些不可预测的原因而没有做足够的测试,从而在强测阶段爆掉。这是个惨痛教训,争取以后的作业中不要再出现这种问题,把失掉的分弥补回来。

前两次作业,几乎没有面向对象的身影,而主要是锻炼程序的鲁棒性。格式识别中种种要求应接不暇,常常是顾此失彼。第三次作业本身就是递归和 is-a 的关系,这就为设计类、继承提供了逻辑基础。说了这么多还没有进入正题,下面分别对三次作业做个简要分析。

1、多项式求导(第一次作业)

其实这次作业完全是面向过程的。多项式可以表示为二元组的列表,这种形式关于求导这种运算是封闭的,也就是说求导依然产生二元组的表。由于涉及到合并同类项,所以使用映射表比较好,即hashmap,指数作为键,系数作为值。这样,在查找、添加、合并的时间复杂度都很低,编码也方便。

性能分唯一得分方式就是合并同类项,系数1不输出,指数为1不输出,0项忽略。另外一个小点就是正项先输出。

这次只有一个主类,一Main到底。因为HashMap已经可以存储所有多项式,就没考虑另定义类。

分模块进行:检查合法性,构造表达式,求导,输出四个主要方法。开始一个大问题是使用长正则匹配整个表达式,所以在字符串长度大于1000的时候会爆掉。后来改用拆项法,每次向后扫描符合条件的项。主要使用的工具是PatternMatcher。这是我第一次思考正则效率的问题,以前只觉得正则强大,没想到引擎是NPC的。这次记住了一个经验:少用星号,少用加号,少用竖线,多用中括号,多用问号。少用 (),多用 (?😃。

Main类结构:

image

度量:

Class OCavg WMC
Main 5 40
Method ev(G) iv(G) v(G)
Main.createPoly() 1 5 7
Main.exitError() 1 1 1
Main.func(int,int) 1 4 4
Main.judge() 1 7 8
Main.main(String[]) 1 1 2
Main.nextStart(int,int,boolean) 6 6 8
Main.output(HashMap<BigInteger, BigInteger>) 3 8 10
Main.qiuDao(HashMap<BigInteger, BigInteger>) 3 4 5

可以看出,在表达式判断合法性、构造和输出方面比较复杂,但我觉得这个比较正常,毕竟格式属于盲搜,而输出需要考虑不同的情况做优化。

互测被测出字符串末尾的加号问题。这是在Matcher扫描过程中出现的错误。这次总体比较满意,美中不足的就是性能分,在一个 -1*x上没有优化成 -x。

2、多项式、三角函数乘积求导(第二次作业)

这次栽了大跟头。由此可知程序测试在工程开发中的重要性。一个小小的replace错误导致强测加互测总共WA了10次。这个错误太致命了,由于split("\\+")是以加号分割字符串,而预处理阶段的干扰加号没有排干净,失之毫厘谬以千里。

这次有点面向对象的感觉,不过,依然几乎是一Main到底,唯一的一个内部类还是当结构体用的,它用来存储sin,cos,x的指数,作为Hashmap的键,系数作为值。在HashMap中,自定义类需要重写equals()hashcode()方法,初步使用了方法重写。扫描方法还和上次一样,只不过多了两种sin(x)和cos(x)。

优化仍然是合并同类项,但是还有sin(x)2+cos(x)2=1这种问题,当时没有过多考虑。后来认真研读了大佬们的算法,才知道这个公式有好多文章可以做。原来面向对象也是考数学的啊。

Main类结构:

image

度量:

Class OCavg WMC
Main 6.1 61
Main.Term 1.33 4
Method ev(G) iv(G) v(G)
Main.Term.Term(BigInteger,BigInteger,BigInteger) 1 1 1
Main.Term.equals(Object) 2 3 4
Main.Term.hashCode() 1 1 1
Main.createExpr() 5 9 11
Main.func(int,int) 1 4 4
Main.gTmD(boolean,String) 2 1 2
Main.judge() 1 9 13
Main.main(String[]) 1 3 3
Main.nextStart(int,int,boolean) 7 8 12
Main.output(HashMap<Term, BigInteger>) 1 15 17
Main.printWrongFormat() 1 1 1
Main.putTerm(HashMap<Term, BigInteger>,Term,BigInteger) 1 2 2
Main.qiuDao(HashMap<Term, BigInteger>) 3 5 6

仍然是和判断、构造、输出有关的函数,复杂度比较高。而且由于在增加了三角函数之后仍然一Main到底,平均复杂度提高了。

3、多项式、三角函数复合求导(第三次作业)

这一次吸取了前一次作业的教训,做了百余数据的测试。但还是被hack了,sin(-1)这种会WF,我的妈呀表达式忘了考虑常数为负数的问题。有惊无险,如果强测多几个这种点我就又玩完了。(正所谓bug是难以避免的,只有神仙才能做到完全没有bug,人与人之间差的只是出bug的几率。但是这个几率,能真正决定一个工程的成败)。

不过半天的思考架构也让我受益匪浅,至少能利用面向对象的思想,把一个具体的事物抽象出数据类型和操作了。真正的数据类型不仅仅是存储,而在于性质和操作的捆绑。

这次是真正体现面向对象威力的时刻。表达式本身就是个抽象概念,所以用了抽象类。表达式要参加运算的,所以定义了加、减、乘、复合四种运算作为具体方法。不同种类的表达式都是表达式,而且都有统一的加减乘复合这些运算,所以继承表达式类,这是 is-a 关系。求导,对不同样式的表达式来说规则不尽相同,而且是分层递归的,所以定义成抽象方法,在表达式类的子类中重写,这是多态。子类呢,分别定义基本表达式类(包括常数、幂函数、三角函数的乘方),加减类(两个表达式通过加减运算形成),乘法类(两个表达式通过乘法运算形成),复合类(两个函数复合的结果)。这样,在创建表达式的过程中,就会自动形成表达式树(二叉树),求导起来也特别方便,直接向下递归即可,而表达式树的叶子节点就是基本类。

这样做的好处是,在表达式格式识别、创建的过程中可以略去括号的影响,宏观考虑,递归向下。有点类似于数据结构课程中做的表达式计算器,当有括号时递归进行。

这次把类设计的各个知识点全部回顾了一遍,包括构造方法、方法重写、抽象类、继承、动态联编(多态)这些。我觉得这次作业是比较经典的面向对象练习,对OO思维是一种很好的锻炼。在前两次锻炼了程序的鲁棒性之后,第三次作业加深了对封装、继承的理解。继承并不仅仅是为了代码复用,还有一个重要作用就是可以实现运行时确定类型,这是OO的精髓所在。(顺带的,把C++中的虚函数、纯虚函数这些概念彻底搞懂了。C++,Java这俩语言感觉真是亲兄弟)

最初想用接口,但考虑到复用性,加、减、乘、复合这些方法无需在每个类中重新写一遍,就改用抽象类了。

UML类图:

image

度量:

类:

Class OCavg WMC
AddSub 1.2 6
Basic 3.75 15
Composite 1 4
Expr 3 24
Main 5.29 74
Multiply 1 4

方法:

Method ev(G) iv(G) v(G)
AddSub.AddSub(boolean,Expr,Expr) 1 1 1
AddSub.getExpr1() 1 1 1
AddSub.getExpr2() 1 1 1
AddSub.isAddOrSub() 1 1 1
AddSub.qiuDao() 2 2 2
Basic.Basic(int,BigInteger) 1 2 3
Basic.getExpClass() 1 1 1
Basic.getPara() 1 1 1
Basic.qiuDao() 10 10 12
Composite.Composite(Expr,Expr) 1 1 1
Composite.getInner() 1 1 1
Composite.getOuter() 1 1 1
Composite.qiuDao() 1 1 1
Expr.add(Expr) 4 4 5
Expr.composite(Expr) 7 7 7
Expr.equalsToOne() 1 2 2
Expr.equalsToZero() 1 2 2
Expr.isConstant() 1 2 2
Expr.isX() 1 3 3
Expr.multiply(Expr) 5 6 7
Expr.sub(Expr) 4 4 5
Main.add(Expr,Expr) 2 2 2
Main.brackets(String) 1 6 7
Main.conditionalPrint(Expr,String) 1 2 2
Main.create(String) 6 8 12
Main.find(Matcher,int,int,int,int) 3 3 5
Main.gI(String,int) 3 2 6
Main.isSin(Expr) 1 2 2
Main.judge(String) 2 10 17
Main.main(String[]) 1 3 3
Main.mul(Expr,Expr) 2 2 2
Main.nextStart(String,int,int,boolean) 7 8 12
Main.output(Expr) 1 16 16
Main.tooBigIndex(BigInteger) 1 3 3
Main.wrongFormat() 1 1 1
Multiply.Multiply(Expr,Expr) 1 1 1
Multiply.getExpr1() 1 1 1
Multiply.getExpr2() 1 1 1
Multiply.qiuDao() 1 1 1

虽然功能比上次复杂,但是OCavg,也就是平均复杂度反而降低了,可见使用类封装、继承的巨大优势。

二、学到了什么

首先就是正则表达式,包括正则的效率问题。然后就是程序的鲁棒性,实际工程中程序对各种不同状况做出的反应都应该符合用户需求。最最重要的就是面向对象的思维。

另外,oo强迫我改掉了不加空格的代码风格。这也是一个巨大的提升,毕竟看那种堆在一起,一个方法几百行,满屏goto等等的代码有多难受,你我都知道。

有痛失分数的懊悔,也有巨大的收获。在这种强大挑战面前,就要有把oo做成软工的思想准备(逃~

三、工具推荐

首先,我推荐用markdown写博客,博客园自带的编辑器超级恶心。可以先用typora编辑好,然后在博客园中设置:管理博客--设置默认编辑器--修改为markdown。之后,把你编辑好的markdown复制到博客的文本框中。可以保存为草稿预览一下。

1、UML工具

idea自带的,选中所有类,右键--Diagrams--Show Diagram

2、复杂度分析工具:MetricsReloaded

idea菜单栏--文件--设置--Plugins--搜索MetricsReloaded--Install--重启idea

安装成功后,打开工程,选择你需要分析的类,然后右键--分析--Calculate Metrics

3、CSV、markdown、HTML、XLS表格转码

网址:tableconvert

idea的Metrics分析完成后,我们可以选择导出到文件(左下角一栏,特别不显眼的地方)。导出的文件是一个CSV格式,我们把它用Excel打开,然后选择一个表格(注意你需要的表格边界,CSV一般没有sheet),复制到剪贴板。

之后打开上述网站,单击上方import选项,把你剪贴板里的东西粘贴到文本框,确定。下面的导出格式选择markdown。OK,最后把生成的markdown表格复制到你的md文件中。

不过,CSV在typora中是可以直接转码的,typora大法好啊!(天灭TinyMCE,退TinyMCE保平安

4、图片--base64转码工具

网址:xpcha

这是一个极好的base64和图片相互转码的工具。我们知道,markdown的图片要么用超链接链接到磁盘、网站,要么用内嵌模式即base64码。而链接到磁盘则无法上传,链接到URL则无法链接本地图片,还需要图床等工具,极不方便。所以,我们用这个网站把图片转化为base64之后,在文件末尾设置链接,在行内进行引用。具体可参考CSDN或简书上的教程。

posted @ 2019-03-25 23:28  wancong  阅读(1785)  评论(3编辑  收藏  举报