BUAA_OO_Unit1总结

Unit 1

总体架构

 

 

 

经过完成本单元的三次作业,我提炼出了上面这一总体架构,三次作业都遵循这一总体架构,下面对这一架构进行简单的解释,详细见各个作业中的分析。

  • 数据

    1. 输入字符串(String)

      输入字符串为原始输入的数据,未经任何处理。

    2. 表达式树一(Expr)

      表达式树一中表达式为树形结构,但其中包含非必要的括号,不满足题目要求。

    3. 表达式树二(SimplifiedExpr)

      表达式树二中表达式为树形结构,其中只包含必要的括号,但长度性能较差。

    4. 表达式树三(SimplifiedExpr)

      表达式树三中表达式为树形结构,且长度性能较优。

    5. 输出字符串(String)

      输出字符串为最终输出的数据。

  • 方法

    1. 解析(parse)

      使用递归下降的方法将线性结构(输入字符串)解析为树形结构(表达式树一)。此时表达式树一中包含非必要的括号,暂且不满足题目的要求,还需进一步化简

    2. 化简(simplify)

      使用递归下降的方法将表达式树一化简为表达式树二。将表达式树一非必要的括号去掉,此外,若两因子可合并,则将指数相加合并为同一因子;若两项可合并,则将系数相加合并为同一项。此时表达式树二中只含必要的括号,满足题目的要求,但长度性能较差,还需进一步优化

    3. 优化(optimize)

      使用递归下降的方法将表达式树二优化为表达式树三。利用三角函数公式对表达式优化使得长度尽可能的短。此时,表达式树三长度得到了减少,满足题目的要求,但仍为树形结构,还需进一步输出

    4. 输出(toString)

      使用递归下降的方法将树形结构(表达式树三)输出为线性结构(输出字符串)。

hw1

  • 总览

     

     

  • 数据

    1. Expr

       

       

    2. SimplifiedExpr

       

       

  • 方法

    1. 解析(parse)

      在Parser类中,对输入的字符串进行递归下降的解析,形成表达式树。

    1. 化简(simplify)

      从上面的类图中可以看到,在Expr相关的类中都有simplify这一方法可以将本类的数据转换为SimplifiedExpr这一类型,SimplifiedExpr实际上为一个哈希表的结构,key为指数,value为系数。以下是各类中simplify方法的具体实现:

      • Expr

        将各个Term所产生的SimplifiedExpr相加得到一个新的SimplifiedExpr。

      • Term

        将各个Factor所产生的SimplifiedExpr相乘得到一个新的SimplifiedExpr。

      • Factor

        将Base所产生的SimplifiedExpr进行次方运算得到一个新的SimplifiedExpr。

      • Var

        SimplifiedExpr中有一个键值对key:1 value:1。

      • Num

        SimplifiedExpr中有一个键值对key:0 value:num。

    1. 优化(optimize)

      因为hw1中只有常量因子和幂函数因子,所以不能够进一步优化,此步骤省略。

    1. 输出(toString)

      SimplifiedExpr这一类型中有toString的方法,将SimplifiedExpr转换为String,并进行一定的长度优化:

      • 指数为0,1,2

      • 系数为0,1,-1

      • 第一项的符号为正

  • 例子

    1. 输入字符串

      -4*x**3*x**2+(x*x**2+1)**2

    2. 表达式树一

    3. 表达式树二

      exponent012345678
      coefficient 1 0 0 2 0 -4 1 0 0
    4. 表达式树三

    5. 输出字符串

      x**6-4*x**5+2*x**3+1

  • 度量

    • 数据

      MethodCogCev(G)iv(G)v(G)
      expr.Expr.Expr() 0 1 1 1
      expr.Expr.addTerm(Term) 0 1 1 1
      expr.Expr.getTerms() 0 1 1 1
      expr.Expr.simplify() 1 1 2 2
      expr.Factor.Factor(Base, int) 0 1 1 1
      expr.Factor.simplify() 0 1 1 1
      expr.Num.Num(BigInteger) 0 1 1 1
      expr.Num.simplify() 0 1 1 1
      expr.Term.Term() 0 1 1 1
      expr.Term.addFactor(Factor) 0 1 1 1
      expr.Term.changeSign() 0 1 1 1
      expr.Term.simplify() 2 1 3 3
      expr.Var.simplify() 0 1 1 1
      main.Main.main(String[]) 0 1 1 1
      parser.Lexer.Lexer(String) 0 1 1 1
      parser.Lexer.getCurToken() 0 1 1 1
      parser.Lexer.getNumber() 2 1 3 3
      parser.Lexer.next() 3 2 3 4
      parser.Parser.Parser(Lexer) 0 1 1 1
      parser.Parser.parseBase() 3 1 3 3
      parser.Parser.parseExponent() 3 1 3 3
      parser.Parser.parseExpr() 8 1 7 7
      parser.Parser.parseFactor() 0 1 1 1
      parser.Parser.parseNum() 5 1 4 5
      parser.Parser.parseTerm() 4 1 4 4
      parser.Parser.parseVar() 0 1 1 1
      simplifiedexpr.SimplifiedExpr.SimplifiedExpr() 0 1 1 1
      simplifiedexpr.SimplifiedExpr.add(SimplifiedExpr) 1 1 2 2
      simplifiedexpr.SimplifiedExpr.getCoefficients() 0 1 1 1
      simplifiedexpr.SimplifiedExpr.multiply(SimplifiedExpr) 6 1 4 4
      simplifiedexpr.SimplifiedExpr.negate() 1 1 2 2
      simplifiedexpr.SimplifiedExpr.pow(int) 1 1 2 2
      simplifiedexpr.SimplifiedExpr.toString() 9 3 7 8
      ClassOCavgOCmaxWMC
      expr.Expr 1.25 2 5
      expr.Factor 1 1 2
      expr.Num 1 1 2
      expr.Term 1.5 3 6
      expr.Var 1 1 1
      main.Main 1 1 1
      parser.Lexer 2 4 8
      parser.Parser 2.88 6 23
      simplifiedexpr.SimplifiedExpr 2.71 7 19
    • 分析

      第一次作业较为简单,所以方法和类的复杂度并不高。

    • 代码行数    
  • bug分析

    hw1的难度较为简单,所以并没有出现bug。

hw2 hw3

我在hw2与hw3的架构大致相同,所以放在一起来解释。

  • 总览

     

     

  • 数据

    1. Expr

       

       

    2. SimplifiedExpr

       

       

  • 方法

    1. 解析(parse)

      在Parser类中,对输入的字符串进行递归下降的解析,形成表达式树。

    1. 化简(simplify)

      从上面的类图中可以看到,在Expr相关的类中都有simplify这一方法可以将本类的数据转换为SimplifiedExpr这一类型,SimplifiedExpr的具体结果如上,以下是各类中simplify方法的具体实现:

      • Expr

        将各个Term所产生的SimplifiedExpr相加得到一个新的SimplifiedExpr。

      • Term

        将各个Factor所产生的SimplifiedExpr相乘得到一个新的SimplifiedExpr。

      • Factor

        将Base所产生的SimplifiedExpr进行次方运算得到一个新的SimplifiedExpr。

      • Var

        SimplifiedExpr中只有x这一项。

      • Num

        SimplifiedExpr中只有num这一项。

      • Func

        将Func中的Expr进行化简得到SimplifiedExpr,并和FuncName一块封装在SimplifiedFunc中,得到新的SimplifiedExpr。

    1. 优化(optimize)

      从上面的类图中可以看到,在SimplifiedExpr相关的类中都有optimize这一方法利用三角函数公式进行长度优化:

      • sin、cos中的表达式第一项符号为正

      • sin²和cos²合并

      • 二倍角公式

      不难发现,优化的顺序以及次数都会影响到最终的结果,为了得到最优的结果,可以重复调用这些优化的方法直到输出的长度不再变化。

    1. 输出(toString)

      从上面的类图中可以看到,在SimplifiedExpr相关的类中都有toString的方法可以将本类的数据转换为String,并进行一定的长度优化:

      • 指数为0,1,2

      • 系数为0,1,-1

      • 第一项的符号为正

  • 例子

    1. 输入字符串

      x*sin((2*x))*2*sin(x)*cos(x)+(x+1)*cos((2*x))**2-cos((2*x))**2

    2. 表达式树一

       

       

    1. 表达式树二

       

       

    2. 表达式树三

       

       

    3. 输出字符串

      x

  • 度量

    • 数据

      MethodCogCev(G)iv(G)v(G)
      simplifiedexpr.SimplifiedTerm.optimize2(SimplifiedExpr) 21 5 10 11
      simplifiedexpr.SimplifiedExpr.toString() 15 3 9 10
      simplifiedexpr.SimplifiedExpr.merge() 44 8 11 13
      simplifiedexpr.SimplifiedExpr.func2(BigInteger, BigInteger, SimplifiedTerm, SimplifiedTerm, SimplifiedTerm, SimplifiedTerm) 14 1 11 11
      parser.Lexer.getFuncName() 17 1 7 9
      expr.Func.isNegative() 4 4 3 4
      ClassOCavgOCmaxWMC
      expr.Func 3.4 6 17
      parser.Lexer 3.2 7 16
      parser.Parser 3.36 7 37
      simplifiedexpr.SimplifiedExpr 3.25 11 52
      simplifiedexpr.SimplifiedTerm 3.3 8 33
    • 分析

      第二三次作业中,复杂度主要来自于两个方面,解析和优化。解析时会出现大量的if语句来对字符串进行解析。优化时也会出现大量的for以及if来进行三角函数公式的优化。

    • 代码行数 
  • bug分析

    • 自己

      我的程序再中测、互测、强测中均没有bug,但在自己刚刚把代码写完时还是有一些bug的:

      • 深拷贝和浅拷贝

        因为在本次作业中使用到了HashMap这一容器,如果对容器中的内容浅拷贝并进行了修改那么就会产生bug,好在这一bug容易发现,再运行了几个测试数据后就会产生空指针的异常。解决方法是使用深拷贝,我的深拷贝是在构造函数中实现的,类似于C++中的拷贝构造函数。

      • 同类型的合并

        我在刚写完代码时,如果输入2*sin(x)*cos(x)*sin((2*x))这一样例,则会输出sin((2*x)),但实际上应该输出sin((2*x))**2,原因是我在将2*sin(x)*cos(x)合并后并没有考虑容器内是否有sin((2*x)),如果有的话需要将指数相加。

      这些bug主要存在于代码复杂度较高的区域。

    • 他人

      对于别人的代码我只发现了一个bug:

      • 类型问题

        sum中的第2、3项有人使用了int进行存储,但实际上会超出long的范围,所以应该用BigInteger进行存储。

总结

  • 架构优缺点

    • 优点

      • 将整个问题分为四个步骤:解析、化简、优化、输出,使得逻辑更加清晰。

      • 采用了两套数据结构:Expr、SimplifiedExpr,使得各个类的复杂度降低。

    • 缺点

      • 在三角函数公式的优化上复杂度较高,容易产生bug。

      • 两套数据结构并无本质的差异,都是在存储表达式,实际上可以简化为一套数据结构。

  • 心得体会

    • 化繁为简

      当我们面临一个复杂的工程时,可以将这一复杂的工程进行适当的简化,并在此基础上进行迭代开发,逐步解决复杂工程。正如本单元的表达式化简,如果我们直接去思考hw3,那必然会非常痛苦,但是我们按照hw1到hw3的顺序进行迭代开发,则会简单很多。

    • 及时重构

      当我们需要对工程的功能进行扩展时,如果我们当前的架构已经不能实现该功能,或者我们为了实现这一功能导致架构很不优雅,这时我们就需要及时对我们的架构进行重构,设计出一个更好的架构。正如我在本单元的架构中使用了两套数据结构,分别为Expr和SimplifiedExpr,实际上可以将两者进行合并从而使用一套数据结构即可。

    • 重设计轻实现

      我们应把大量的时间花费在设计上,而不是急于实现。当我们设计完成后,我们应该要知道我们的代码可以完成哪些功能、不可以完成哪些功能。实现只是对设计的代码化,个人认为这是一个比较机械的过程。如果并没有完成设计就开始了代码实现,此时思路并不清晰,极易出现bug,而且当这一设计走不通时,代码还要重写。

    • 注重OOP思想

      由于继承、封装、多态的特性,使得代码更好扩展。同时,面向对象编程也符合人类对事物的认识。

posted @ 2022-03-26 15:40  隐姓埋名4567  阅读(21)  评论(0编辑  收藏  举报