面向对象第一单元总结:Java实现表达式求导

题目要求

  • 输入一个表达式:包含x,x**2,sin(),cos(),等形式,对x求导并输出结果
  • 例:\(x+x**2+-2*x**2*(sin(x**2+(cos(x**2))))\)

思路分析

  • 表达式拆解

    • 将一个表达式分为三级:表达式Polynomial、项Term、因子Unit
    • 一个Poly由Term累加构成,一个Term由Unit累乘构成
  • 解题思路

    • 读入表达式字符串,将其分解成一个表达式类
    • 对表达式类求导
    • 输出导数表达式的字符串形式
  • 字符串->表达式

    • 本人采用递归下降的思路,下面简述递归下降算法:

    • 假设一个类A的字符串形式是"BCD"(拼接),B的形式是"x*E",c是sin(x),D是(A),E是+,那么我们要分析字符串A,就可以这样写

    •   public A getA(){
      A ans = new A(); B newB = getB();
      C newC = getC();
      D newD = getD(); ans.addB(newB); ans.addC(newC); ans.addD(newD);
      return ans;
      }
      public B getB(){
      B ans = new B(); if(接下来是"x*"){
      走2步;
      } else {
      throw 格式错误异常;
      }
      E newE = getE(); ans.addE(newE);
      return ans;
      }
      public C getC(){
      C ans = null;
      if(接下来是"sin(x)"){
      ans = new C();
      走6步;
      } else {
      throw 格式错误异常;
      }
      }
      public D getD(){
      D ans = null;
      if(接下来是"("){
      走1步;
      } else {
      throw 格式错误异常;
      }
      A newA = getA();
      ans.addA(newA);
      if(接下来是")"){
      走1步
      } else {
      throw 格式错误异常;
      }
      return ans;
      }
      ...//剩下的就不写了,主要思路已经阐释清楚
    • 这样,从最大的一级,逐级递归的找更小的构成单元,直到找到以字符为匹配单元的最小单元,由此可以对符合一定语法的字符串进行全面分析

    • 现在再看,我设计的递归下降其实糅合了递归下降和类的生成,这就使得耦合度过高,较为高明的办法应该是使用工厂模式,建立一个MFactory类,在递归下降中,将匹配到的字符串交由工厂生成一个相应的对象,而不是自己new一个出来

递归下降用到的类

  • ExpressionString

    • 由于在递归下降的过程中,经常需要移动指针、判断字符,因此将这部分功能抽取出来作为一个新的工具类,减小递归下降类的复杂度

    • 成员由一个String存放表达式,一个Integer存放当前位置

    • ExpressionString实现的方法主要是

    •   public void moveSpace(); //跳过当前位置向后的空格
      public void moveForward(int step); //位置向前移动step步
      public boolean isEnd(); //判断是否读到字符串末尾
      public boolean isStartWithChar(char c); //判断是否以c字符开头
      public boolean isStartWithString(String s); //判断是否以s字符串开头
      ...
    • 这样的设计,减少了递归下降过程中代码的重复,增加了可读性,使思路更加明晰

  • ExpressionParser

    • 实现了上面描述的递归下降算法,不再赘述,复杂度如下

      Method CogC ev(G) iv(G) v(G)
      ExpressionParser.ExpressionParser(String) 0.0 1.0 1.0 1.0
      ExpressionParser.getConstant() 0.0 1.0 1.0 1.0
      ExpressionParser.getCos() 18.0 8.0 10.0 11.0
      ExpressionParser.getMultiplicationSign() 3.0 3.0 3.0 3.0
      ExpressionParser.getOperator() 4.0 4.0 4.0 4.0
      ExpressionParser.getPoly() 1.0 1.0 2.0 2.0
      ExpressionParser.getPolyUnit() 2.0 2.0 2.0 2.0
      ExpressionParser.getPower() 12.0 5.0 7.0 8.0
      ExpressionParser.getSignedInteger() 6.0 3.0 3.0 5.0
      ExpressionParser.getSin() 18.0 8.0 10.0 11.0
      ExpressionParser.getTerm() 3.0 1.0 4.0 4.0
      ExpressionParser.getUnit() 7.0 6.0 8.0 8.0
      ExpressionParser.isEnd() 0.0 1.0 1.0 1.0
      Total 74.0 44.0 56.0 61.0
      Average 5.6923076923076925 3.3846153846153846 4.3076923076923075 4.6923076923076925
    • 由于需要分析各种不同类型的格式错误,因此不可避免的在某些方法中的判断语句过多

  • InputWongFormatException

    • 自定义的异常类,继承自Exception,用于在检测到输入格式错误时抛出异常

    • 虽然只需要输出一个WONG FORMAT!!!,但也要符合面向对象的设计,Java的抛出异常机制非常实用,建议好好学习一下

    • 类内虽然只有几行,但也是符合面向对象的代码:

    •   private String exceptionInfor;
      public InputWrongFormatException() { //在构造函数中初始化,这样方便应对未来多变的需求
      exceptionInfor = "WRONG FORMAT!";
      }
      public String getExceptionInfor() { //不要将错误信息设置为public,而是留下相应的方法
      return exceptionInfor; //这样外部只需要System.out.println(e.getExceptionInfor());即可
      }

内核各个类的创建、思路分析

  • 类图:

  • 为了放置和内置库冲突,在自定义的类之前加一个M表示My

  • MPolynolmial(多项式)

    • MPolynomial类代表了一整个表达式

    • 其基本形式是许多项的和,因此也可以看成是一个项的累加类

    • 成员只有一个项的ArrayList

    • Method CogC ev(G) iv(G) v(G)
      MPolynomial.addPoly(MPolynomial) 8.0 4.0 5.0 5.0
      MPolynomial.addTerm(MTerm) 4.0 3.0 4.0 4.0
      MPolynomial.addTerm(String,MTerm) 2.0 1.0 3.0 3.0
      MPolynomial.clone() 2.0 1.0 3.0 3.0
      MPolynomial.getDerivation() 1.0 1.0 2.0 2.0
      MPolynomial.MPolynomial() 0.0 1.0 1.0 1.0
      MPolynomial.toString() 6.0 1.0 4.0 6.0
      Total 23.0 12.0 22.0 24.0
      Average 3.2857142857142856 1.7142857142857142 3.142857142857143 3.4285714285714284
    • 类的主要方法如上,可以看到每一个方法都较为简短

    • 下面以MPoly中的几个方法说明面向对象的思想

      • getDerivation()
        • 由于MPoly是一个累加类,因此他的导数就等于各个项的导数的和,因此函数体为

        •   MPolynomial poly = new MPolynomial();
          for (MTerm term : terms) {
          MPolynomial derivation = term.getDerivation();
          poly.addPoly(derivation);
          }
          return poly;
        • MPoly类不会考虑如何得到一个项的导数,他会把这项任务交给一个MTerm(项)去做,MPoly只管跟一个MTerm要他的导数

        • 需要注意的是MPoly的导数依旧是一个MPoly,因为\((a+b)'=a'+b'\)依旧是一个多项式,只有在Main函数的最后输出时,再调用MPolytoString()方法获得字符串形式

      • toString()
        • 到得到MPoly的字符串形式,只需要获得每一项的的字符串,并拼接起来即可

        • 其中有一些小小的细节需要考虑,比如项的第一个字符需要是正负号

        • 因此函数体为

        •   String ans = "";
          for (MTerm term : terms) { //最重要的6行代码
          String next = term.toString();
          if (!next.equals("+0") && !next.equals("0")) { //小优化:如果Term是0,则跳过
          ans += next;
          }
          }
          if (ans.equals("")) { //如果到最后还是0,则ans为“0”
          ans = "0";
          }
          if (ans.charAt(0) == '+') { //如果第一个字符为'+',则删掉
          ans = ans.substring(1);
          }
          return ans;
      • addPoly()
        • getDerivation()中,我们得到的每一个MTerm的导数都是一个MPoly,每一次需要将新的多项式加到已有的多项式中,因此需要一个addPoly(MPolynomial newPoly)方法

        • 我们需要遍历已有的MTerm,每一次再遍历新的newPoly的所有项,如果两个项能够合并,就合并他们(这其实是一个优化过程,也可以不判断直接加到MTermArrayList中),因此函数体为

        •   for (MTerm newTerm : poly.terms) {
          boolean foundMergableTerm = false; //记录是否找到了一个可以合并的项
          for (MTerm term : terms) {
          if (term.isMergable(newTerm)) {
          term.merge(newTerm);
          foundMergableTerm = true;
          break;
          }
          }
          if (!foundMergableTerm) {
          terms.add(newTerm);
          }
          }
      • 相信这几个例子就能够展现面向过程的魅力了。一个类不会过多的考虑除了自己意外的细节,把能交给别人做的事情都交给别人,自己只管提出需求。这样写代码会思路清晰,维护简单且可读性好

  • MTerm(项)

    • MTerm类代表了一个类

    • 其基本形式是许多因子的成绩,因此也可以看成是一个因子的累乘类

    • 成员只有一个因子的ArrayList

    • Method CogC ev(G) iv(G) v(G)
      MTerm.addUnit(MUnit) 4.0 3.0 4.0 4.0
      MTerm.addUnits(ArrayList) 1.0 1.0 2.0 2.0
      MTerm.clone() 2.0 1.0 3.0 3.0
      MTerm.getDerivation() 6.0 1.0 4.0 4.0
      MTerm.isEmpty() 0.0 1.0 1.0 1.0
      MTerm.isMergable(MTerm) 16.0 4.0 5.0 8.0
      MTerm.merge(MTerm) 10.0 6.0 2.0 6.0
      MTerm.MTerm() 0.0 1.0 1.0 1.0
      MTerm.toString() 9.0 3.0 4.0 8.0
      Total 48.0 21.0 26.0 37.0
      Average 5.333333333333333 2.3333333333333335 2.888888888888889 4.111111111111111
    • 绝大部分方法依然比较简短,只有在判断两个项是否需要合并时较为复杂,不具有普遍意义

    • 具体的实现就不再赘述,与MPolynomial的思想是完全相同的,将更基础的事交给底下的MUnit(因子)去做

  • MUnit(因子)

    • MUnit是所有因子的父类,可以定义为一个抽象类或者是借口,因为我们不需要创建一个MUnit对象,只会创建如cos,sin,x**2等因子对象

    • MUnit规定子类需要实现的方法有:

      •   public ArrayList<Munit> getDerivative(); //计算因子自己的导数
        public boolean isMergale(MUnit newUnit); //判断自己能否与另一个因子合并
        public void merge(MUnit newUnit); //与另一个能够合并的因子合并
        public String toString(); //返回自己的字符串形式
        public boolean equals(Object o); //判断自己是否与另一个因子相等
        public MUnit clone(); //将自己深层克隆一份
  • MConstant(常数因子)

    • 幂函数、三角函数、表达式因子等其他因子之前没有“系数”这一成员,而是用一个常数因子作为整个项的系数
    • 不在赘述其细节
  • MPower(幂函数)

    • 幂函数主要由指数组成,我的设计中没有“变量”这一个概念,而是用幂函数来代替“变量”,其他的如\(sin(x)**2\)不是幂函数,而是三角函数,三角函数自己有一个指数

    • 因此,如果要增加多变量求偏导数的需求,只需要更改MPower类的内容,增加成员“变量”,并修改求导、求字符串等函数即可

    • 细节不再赘述,其复杂度如下。可以看到也较为简单

    • Method CogC ev(G) iv(G) v(G)
      MPower.clone() 0.0 1.0 1.0 1.0
      MPower.equals(MUnit) 1.0 1.0 2.0 2.0
      MPower.getDerivative() 0.0 1.0 1.0 1.0
      MPower.getExponent() 0.0 1.0 1.0 1.0
      MPower.isMergable(MUnit) 0.0 1.0 1.0 1.0
      MPower.merge(MUnit) 0.0 1.0 1.0 1.0
      MPower.MPower(BigInteger) 0.0 1.0 1.0 1.0
      MPower.MPower(BigInteger,MUnit) 0.0 1.0 1.0 1.0
      MPower.MPower(String) 0.0 1.0 1.0 1.0
      MPower.toString() 3.0 1.0 3.0 3.0
      Total 4.0 10.0 13.0 13.0
      Average 0.4 1.0 1.3 1.3
  • MTrigonometric(三角函数)

    • 这是MCosMSin的父类,只是为了增加程序的扩展性,在这三次作业中没起到任何作用,也没有任何有意义的、与MUnit不重复的方法
  • MSin(sin函数) MCos(cos函数)

    • 一个BigInteger成员作为指数,一个MUnit成员作为内函数(只有第三次作业从单独的x变为一个因子

    • 其余不再赘述,复杂度如下

    • Method CogC ev(G) iv(G) v(G)
      MSin.clone() 1.0 1.0 2.0 2.0
      MSin.equals(MUnit) 1.0 1.0 3.0 3.0
      MSin.getDerivative() 1.0 1.0 2.0 2.0
      MSin.isMergable(MUnit) 1.0 1.0 2.0 2.0
      MSin.merge(MUnit) 0.0 1.0 1.0 1.0
      MSin.MSin() 0.0 1.0 1.0 1.0
      MSin.MSin(BigInteger,MUnit) 0.0 1.0 1.0 1.0
      MSin.toString() 3.0 1.0 3.0 3.0
      Total 7.0 8.0 15.0 15.0
      Average 0.875 1.0 1.875 1.875
  • MPolyUnit(表达式因子)

    • 在第二次作业中,加入了表达式因子这种东西,如\(x**2*(sin(x)+3)\)
    • 一个MPolyUnit只有一个MPolynomial成员,两者的本质区别就在于,MPolyUnit继承自MUnit,以及比MPolynomialtoString()中两侧套了一组括号,其余方法均直接调用MPolynomial的同名方法

程序中的bug

  • 面向对象编程的一大特点就是——bug少,而且容易找到。编写内核代码的过程十分顺畅,且几乎不产生bug,即使有,也很快就能找得出。

  • 我认为,原因就在于面向对象编程时,会将一个任务分解成一个个小任务,并且任务之间的耦合度比较低

  • 比如说,我需要完成“表达式求导”这一项任务,那么一个MPoly需要做的就是以下几步:

    1. 创建一个新的表达式
    2. 遍历自己的每一项
    3. 获得每一项的导数
    4. 将获得的导数添加到新的表达式中
    5. 遍历完成,返回新的表达式
  • 这五个步骤几乎不涉及到任何的细节问题,而是对任务做出规划,因此这一段代码出bug的概率极低

  • 这样一层一层的将任务细化,最终只有很少部分的方法需要考虑细节问题,大大降低了bug的可能性

  • 测评中出现的bug

    • 测评过程中,仅出现过细节方面的错误,比如一个指数比long大、在获得字符串时脑子一抽多写了一句话,导致复杂度为\(n!\)而TLE,均未涉及到内核本质

发现别人程序bug

  • 很惭愧,我并没有设计对他人程序的测评机,所以这一部分没有可写的

重构经历总结

  • 我并没有看过往届学长学姐的博客,自己从头开始思考的整个结构,很幸运的是三次作业中我基本不需要重构内核代码。
  • 第一次作业我使用的是正则表达式分析字符串,第二次作业引入了“表达式因子”这一东西导致正则表达式完全失效,必须要改掉“分析表达式”这一部分的代码。
  • 每一次增加新的要求,就把这项要求再拆分成小的需求,在相应的类里添加一小段代码即可实现。这就是面向对象的好处吧。

心得体会

  • 从开学第一次OO课开始,需要完成的任务如海一般,一想到这些我就上火,然后就口腔溃疡了一个周没好好吃饭T^T
  • 但是OO课的设计确实能让人学到很多很多知识,几次作业迭代的需求也恰到好处的恶心你但是又让你能够做出来,在这个过程中大大提高了OO的意识
  • 如果学弟学妹们在OO课之前看到这篇帖子,一定现在立刻马上去学Java,并做完OO的Pre预习任务,否则开学以后苦不堪言。OO一开课,他就默认你精通Java了,不要被Pre里仿佛从0开始教你Java的假象给骗了,学不到精通的程度完全无法应付OO课的编程需求。
  • 感谢课程组给了我痛并快乐着的机会:)

面向对象第一单元总结:Java实现表达式求导的更多相关文章

  1. JAVA实现表达式求导运算的分析总结

    1第一次作业 1.1题目描述 对形如4*x+x^2+x的多项式求导. 1.2类图 1.3度量分析 在完成第一次作业时,我的写法没有特别的"面向对象".唯一封装起来的是Node,代表 ...

  2. BUAA-OO-第一单元表达式求导作业总结

    figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...

  3. OO_Unit1_表达式求导总结

    OO_Unit1_表达式求导总结   OO的第一单元主要是围绕表达式求导这一问题布置了3个子任务,并在程序的鲁棒性与模型的复杂度上逐渐升级,从而帮助我们更好地提升面向对象的编程能力.事实也证明,通过这 ...

  4. 2020 OO 第一单元总结 表达式求导

    title: BUAA-OO 第一单元总结 date: 2020-03-19 20:53:41 tags: OO categories: 学习 OO第一单元通过三次递进式的作业让我们实现表达式求导,在 ...

  5. OO面向对象第一单元总结

    OO面向对象第一单元总结(表达式求导) 写在前面: 魔鬼课程oo第一单元终于结束,当终究要落笔总结,竟不知从何写起…… 回首再去看第一次的作业,你会满足于那时的幸福,或许,这就是成长吧! 千言万语,一 ...

  6. 2019年北航OO第1单元(表达式求导)总结

    2019年北航OO第1单元(表达式求导)总结 1 基于度量的程序结构分析 量化指标及分析 以下是三次作业的量化指标统计: 关于图中指标在这里简要介绍一下: ev(G):基本复杂度,用来衡量程序非结构化 ...

  7. Java描述表达式求值的两种解法:双栈结构和二叉树

    Java描述表达式求值的两种解法:双栈结构和二叉树 原题大意:表达式求值 求一个非负整数四则混合运算且含嵌套括号表达式的值.如: # 输入: 1+2*(6/2)-4 # 输出: 3.0 数据保证: 保 ...

  8. OO_JAVA_表达式求导_单元总结

    OO_JAVA_表达式求导_单元总结 这里引用个链接,是我写的另一份博客,讲的是设计层面的问题,下面主要是对自己代码的单元总结. 程序分析 (1)基于度量来分析自己的程序结构 第一次作业 程序结构大致 ...

  9. OO Unit 1 表达式求导

    OO Unit 1 表达式求导 面向对象学习小结 前言 本博主要内容目录: 基于度量来分析⾃己的程序结构 缺点反思 重构想法 关于BUG 自己程序出现过的BUG 分析⾃己发现别人程序bug所采⽤的策略 ...

随机推荐

  1. Prometheus(一):Web服务环境监控

    写在前面 现每个后端的同学的日常都在跟服务(接口)打交道,维护老的比较大单体应用.按业务拆得相对比较细的新服务.无论企业内部用的,面向用户的前端的服务.流量大的有流量小的,有重要的有不那么重要的. 但 ...

  2. SpringMVC(11)表单标签

    本篇我们来学习Spring MVC表单标签的使用,借助于Spring MVC提供的表单标签可以让我们在视图上展示WebModel中的数据更加轻松. 一.首先我们先做一个简单了例子来对Spring MV ...

  3. 基于FPGA的图像镜像

    图像镜像,一种较为常见的图像处理操作,分为水平镜像.垂直镜像.对角镜像.水平镜像即处理后的图像与原图像关于垂直线对称,垂直镜像为处理后的图像与 原图像关于水平线对称,对角镜像则关于对角线对称. 关于低 ...

  4. Java核心基础第5篇-Java面向对象_类和对象

    Java面向对象之类和对象 一.面向对象概述 Java是完全的面向对象编程(Object Oriented Programming),简称OOP. 面向对象编程的思维方式更加符合大家的日常生活,因为我 ...

  5. MyEclipse无法打开jsp文件(打开是空白的),但是可以打开java文件

    转载: 解决MyEclipse使用时打开JSP发生"An error has occurred,See error log for more details"错误的解决方法这个问题 ...

  6. C语言变量为何先定义后使用

    C语言中,对变量的使用,首先要先定义.说明其数据类型.原因可能如下: 1不同类型的变量,其编码表示方式可能不同. 2不同类型的变量,其占有的空间大小不同.不事先说明无法在内存中开辟空间.

  7. springboot-2-IOC容器的基本注解

    @Configuration 告诉springboot这是一个配置类,用于补充IOC容器, 示例: @Configuration //告诉springboot这是一个配置类,作为IOC容器的自定义补充 ...

  8. PAT甲级:1066 Root of AVL Tree (25分)

    PAT甲级:1066 Root of AVL Tree (25分) 题干 An AVL tree is a self-balancing binary search tree. In an AVL t ...

  9. java常见的面试题(二)

    1.mybatis 中 #{}和 ${}的区别是什么? #{}是预编译处理,${}是字符串替换: Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的 ...

  10. gitbook导出markdown文件为pdf格式

    前言本人想完成如题所示的操作,可是兜兜转转在网上搜了好久,不是Linux下的教程就是mac OS下的教程,而且一大堆windows的教程能够说明白的寥寥无几,本着分享的态度,鄙人今天特整理教程如下: ...