OOP 第一章作业总结

程序设计结构分析

类图分析

  • 第一次作业
    由于第一次作业完成的功能比较简单,而且出于对面向对象设计理念不熟悉(其实现在也不是很熟悉,逃),整个程序设计的非常简单。通过类图(见下)可以看出,程序只有两个类:PolyCal 包含 main 方法,充当表达式类的功能,并且完成对输入的解析;Poly 充当项类,管理一个项内的因子,并向 PolyCal 提供服务。

    在这里插入图片描述

  • 第二次作业
    这次作业虽然较上一次而言,划分出了很多类,但其实与第三次作业助教提示的设计架构仍有较大的差距,面向过程的意味仍然非常明显。PolyCal 仍然是充当表达式类,包含 main 方法,并且自己解析输入,完成求导并化简等诸多功能,剩下的其他类都是充当它的辅助,可以看出 PolyCal 类的功能过于复杂,不可取。
    在这里插入图片描述

  • 第三次作业
    借由助教的提示,这次将 Main 单列成一个类,而且完成逻辑业务的框架由三个大类组成,表达式、项类、因子类。Main 类负责创建并维护一个 Expression 类的实例,将初步处理过的输入交给表达式类解析,表达式类的解析则是表达式创建项类将输入交给他们解析。不过,这个架构也有不足之处,第一,我把优化输出即去掉多余括号的大多数工作交给了 toString() 方法,这直接导致了后面遇到了未知的 TLE bug,调式时没法查看当前变量,因为查看会调用 toString() 方法,而 toString() 出了 bug;第二,对于输入解析的处理仍然非常暴力,直接导致了 Poly 类复杂度爆炸。
    在这里插入图片描述

复杂度分析

由于方法复杂度的截图过长(方法数太多了,严重影响阅读体验),所以这里只挑我发现的重点问题说。三次作业方法复杂度超标比例为 1:3/12;2:5/28;3:17/53,类复杂度如下图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 分析总结
  1. 由于方法中使用了大量的 if-else 控制语句,导致三次作业的部分方法圈复杂度比较高
  2. 由于并未将解析表达式的功能单列出来,导致了第一、二次作业 PolyCal 与第三次作业 Poly 类复杂度偏高
  3. 由于将优化放在了表达式类中,导致后两次作业的 PolyCal, Expression 类的复杂度偏高

自己程序的bug -- 优化 bug

后两次作业强测中挂掉的数据点,最后发现都是由于自己优化的锅,在这里检讨一下。

  • 第二次作业

    • 第二次作业主要是三角函数的优化,当时我采用的是数学公式硬优化,从 sin(x)^2 + cos(x)^2 = 1 开始推导,得到了在一定前提条件的情况下,能够实现三角函数化简。但是最后写代码的时候把 if 条件语句的前提条件写错了。仔细想一下,虽然这只是由于一个失误造成的 bug,但是这种单靠数学公式推导的方式来进行化简的思路,本身就是足够复杂的,不能算得上比较好优化方法。
    • 这里分享一下研讨课上听到某位大佬介绍的优化方法 -- 启发式算法

    一个基于直观或经验构造的算法,在可接受的花费(指计算时间和空间)下给出待解决组合优化问题每一个实例的一个可行解

    • 本次作业的优化,就是基于 sin(x)^2 + cos(x)^2 = 1 这条经验,通过迭代循环优化来实现的。起始,将未优化表达式放入 TreeMap 中,每次迭代循环过程中,将 TreeMap 中最短的表达式取出,随机选择其中一项将其中的 cos(x)^2 或 sin(x)^2 化成 1 - sin(x)^2 或 1 - cos(x)^2,再去合并同类项,将新的表达式放入 TreeMap 中。这样做,虽然并不能保证一定能优化出优于原解的表达式,但其执行时间可控,可以在一定程度上避免自己的程序因为优化而造成 TLE
    • 关于更多启发式算法,这里推荐一篇帖子
  • 第三次作业
    第三次作业,我想采取的优化也仅仅是去掉多余的括号,如果项中含有一个因子为 0(或 sin(0) 等)那么整个项 toString 返回一个空串,以及如果表达式因子仅含有一个项,那么输出它的时候,就去掉括号,以及尽可能的合并同类项。现在回顾看来,这些优化的实现方法都存在问题,下面分享一下心得体会。

    1. 表达是因子的去括号的操作应该交给字符串输入解析类来完成,即通过栈来检查括号匹配,查看是否首尾括号恰好内嵌一对括号。
    2. 注意 ArrayList.addAll() 是浅拷贝,意味着,它只是把索引赋了过来,若后面对索引对象进行了值修改,会导致本不想修改的原对象也被修改了。这里建议以后尽量采用不可变对象,任何修改值的方法都是返回一个新的对象。
    3. 不应该将优化操作放在 toString() 方法内部

互测发现别人的 bug

由于后面两次作业优化都出了巨大 bug,所以被分到了 bug 较多的 C 组,所以检查别人 bug 难度相对较低,我采取的是,先把所有人的代码文件夹集中到一个目录下,先通过 javac 编译,然后就可以用 .sh 文件快乐的,一次测所有人的代码了。除此之外,还用 python 的 Xeger 和 sympy 包,搭了一个简易的自动对拍器,但遗憾的是,正则表达式没法生成嵌套,所以第三次作业并没能很好的自测到自己的 bug。

应用工厂模式解析输入字符串

因为观察到,由项来实现对字符串的解析取出每一个因子然后生成对应的因子对象,导致项类的复杂度过高;而且不同种类的因子生成都非常类似,找到对应的字符串,传递给构造器即可。所以可以创建一个工厂接口,为每位因子实现一个工厂生成方法,在项类仅需要通过正则表达式判断一下当前需要解析的因子是哪一种的,然后就可以通过工厂接口变量实例化对应的工厂方法,来生成对应的因子,减轻项类的工作负担。

public interface ParseFactor{
	public ArrayList<Object> parse(String input);
	// 因为我不仅需要返回新建的因子对象而且需要返回解析过剩下的字符串
}
posted @ 2019-03-26 00:32  Melles  阅读(272)  评论(0编辑  收藏  举报