BUAA OO 第一单元总结

一、第一次作业

(1)类结构设计

第一次题目架构比较简单,具体结构设计如下(图1):

  • 基本数据类型:
    • Polynomial:用Hashmap<int(指数), BigInteger(系数)>将表达式/项/因子数据内容统一形式
      • 运算方法:加法add、乘法mul、乘方pow
      • 格式化输出:toString方法将其内容输出(含正项提前处理),addPowerFunc负责将每个项转化为字符串形式。
  • 表达式解析:
    • Lexer :使用正则表达式(?<num>(?<=[^\\dx)]|^)[+-]?\\d+|x)|(?<opt>\\*\\*|[)(+\\-*])匹配每个有效字符串,得到形如 {"-","-","-2","*","(","x",")"} 的字符串序列
    • parser:使用递归下降法对得到的字符串序列进行解析,得到表达式/项/因子实例对象。
  • 表达式储存
    • Expr:表达式
      • 运算方法:updateterms中的Polynomial相乘后,在乘power次幂并记录到res中;negateres取负。
      • 更新方法:addTerm增加新的项
      • 输出方法:toString调用res.toString
    • Term:项
      • 运算方法:updatefactors中的Polynomial并记录到res中,negateres取负。
      • 更新方法:addFactor增加新的因子
    • Factor(接口):包含ExprValueFactor
      • 运算方法:negateres取负
      • 取值方法:getRes获得Factor中数据值
      • 接口实现类:
        • Expr:表达式
        • ValueFactor:常数(幂次为0)以及幂函数

(2)度量分析

从图中可以看出PolynomialParser类OCavg较高,高复杂度在于递归调用(Parser)和双重循环(Polynomial)。

(3)Hack情况

  • 己方bug分析:本次作业个人在强测和弱侧中均未出现bug
  • 对方bug分析:
    • 解析- -时出错

二、第二次作业

(1)类结构设计

第二次作业增加了三角函数、自定义函数、求和函数,代码量相较于第一次有明显增长,不过整体架构上没有太大变化。值得一提的是在处理自定义函数/求和函数时,个人的处理方法是先把原表达式以字符串形式储存下来再用类似Parser的方法解析,由于这样Function和Parser的方法有相似之处(也为了后续嵌套表达式处理),因此将Parser和Function进行了合并(由于耦合过高,第三次作业将Function类进行了解耦)

  • 基本数据类型:
    • Polynomial:用Hashmap<Hashmap<Factor, int(指数)>, BigInteger(系数)>将表达式/项/因子数据内容统一形式。其中Factor有且仅有三角函数、幂函数、常数。****
      • 运算方法:加法add、乘法mul、乘方pow
      • 格式化输出:toString方法将其内容输出(含正项提前处理),addPowerFunc负责将每个项转化为字符串形式。
      • 判断相等方法:hashCode equal
      • 化简方法:
        • replaceSinX2/replaceCosX2:负责形如 \(asin(x)^2+bcos(x)^2+c\) 平方和(其中\(a\)\(b\)\(c\)\(x\))均为任意因子/项。
        • replaceSinX4:负责形如 \(a(sin(x)^4-cos(x)^4)\)的化简。
        • buildSinXnPowers/buildCosXnPowers/buildConstXnPowers:在化简过程中构造含 \(sin(x)^n\) / \(cos(x)^n\) / 去掉对应三角函数的项
  • 表达式解析:
    • Lexer :使用正则表达式((?<=[^\w)]|^)[+-]?\d+)|(\*\*|[)(+\-*])|(sin|cos|sum)|([a-z,])匹配每个有效字符串,得到形如 {"-","-","-2","*","(","x",")"} 的字符串序列(New!)
    • Function
      • 解析方法:
        • parseXXX:使用递归下降法对得到的字符串序列进行解析,得到表达式/项/因子实例对象或者对幂次解析。
        • accessSign:对表达式/项首项符号的解析
      • 计算方法:
        • calculate:调用后传入参数,得到自定义函数的值(Expr)。
  • 表达式储存
    • Expr:表达式
      • 运算方法:updatePowerrespower次幂并记录到res中;updateterms中的Polynomial相乘后记录到res中;negateres取负。
      • 更新方法:addTerm增加新的项
      • 输出方法:toString调用res.toString
      • 化简方法:simplyfy将调用res.simplyfy
      • 判断相同方法:hashCode equal
      • 复制方法:deeplClone创造一个新的Expr
    • Term:项
      • 运算方法:updatefactors中的Polynomial并记录到res中,negateres取负。
      • 更新方法:addFactor增加新的因子
      • 判断相等方法:hashCode equal
    • Factor(接口):
      • negate:将res取负
      • getRes:获得Factor中数据值
      • deepClone:深拷贝
      • setPower:设置Factor的幂次
      • 接口实现类
        • Expr:表达式
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • Var:幂函数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • Constant:常数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • TtrigonometricFactor:三角函数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
          • 子类:
            • Sin:正弦函数
            • Cos:余弦函数

(2)度量分析

从图中可以看出Poly类的OCavg和WMC较高,Function类(原Parser类)的WMC较高,原因同第一次作业。

(3)Hack情况

  • 己方bug分析:本次作业个人在强测未出现bug,互测出现两个bug
    • sin(0)**0 解析出错:原因是如果检查到sin(0)时直接返回常数0,未考虑幂次
    • 化简含cos(x)**2的表达式时出错:原因是构建时把含cos的项误写成含sin的项
  • 对方bug分析:
    • sin(0)**0解析出错
    • sin(-1)**2解析出错(底数换成-sin(1)后没有考虑幂次对最终符号的影响)

三、第三次作业

第三次作业允许三角函数/自定义函数的嵌套,相较于第二次作业几乎没有改动,仅仅对Poly类和Function类做了解耦,提取了部分方法,同时改变了一些方法/类名,增加可读性。

(1)类结构设计

  • 基本数据类型:
    • Polynomial:用Hashmap<Hashmap<Factor, int(指数)>, BigInteger(系数)>将表达式/项/因子数据内容统一形式。其中Factor有且仅有三角函数、幂函数、常数。****
      • 运算方法:加法add、乘法mul、乘方pow
      • 格式化输出:toString方法将其内容输出(含正项提前处理),addPowerFunc负责将每个项转化为字符串形式。
      • 判断相等方法:hashCode equal
      • 判断数据类型方法:equalX2equalConstantequalExpr
    • PolySimplyfy:化简方法类
      • handleSinX2/handleCosX2:负责形如 \(asin(x)^2+bcos(x)^2+c\) 平方和(其中\(a\)\(b\)\(c\)\(x\))均为任意因子/项。
      • handleSinX4:负责形如 \(a(sin(x)^4-cos(x)^4)\)的化简。
      • buildSinXnPowers/buildCosXnPowers:在化简过程中构造含 \(sin(x)^n\) / \(cos(x)^n\) / 去掉对应三角函数的项
      • replaceFactor:替换项中的Factor
      • handleSinXCosX:处理Sin二倍角化简
      • handleCos2X:处理Cos二倍角化简
  • 表达式解析:
    • Lexer :使用正则表达式((?<=[^\w)]|^)[+-]?\d+)|(\*\*|[)(+\-*])|(sin|cos|sum)|([a-z,])匹配每个有效字符串,得到形如 {"-","-","-2","*","(","x",")"} 的字符串序列(New!)
    • Parser
      • 解析方法:
        • parseXXX:使用递归下降法对得到的字符串序列进行解析,得到表达式/项/因子实例对象或者对幂次解析。
        • accessSign:对表达式/项首项符号的解析
    • Function
      • 计算方法:
        • calculate:调用后传入参数,得到自定义函数的值(Expr
  • 表达式储存
    • Expr:表达式
      • 运算方法:updatePowerrespower次幂并记录到res中;updateterms中的Polynomial相乘后记录到res中;negateres取负。
      • 更新方法:addTerm增加新的项
      • 输出方法:toString调用res.toString
      • 化简方法:simplyfy将调用res.simplyfy
      • 判断相同方法:hashCode equal
      • 复制方法:deeplClone创造一个新的Expr
    • Term:项
      • 运算方法:updatefactors中的Polynomial并记录到res中,negateres取负。
      • 更新方法:addFactor增加新的因子
      • 判断相等方法:hashCode equal
    • Factor(接口):
      • negate:将res取负
      • getRes:获得Factor中数据值
      • deepClone:深拷贝
      • setPower:设置Factor的幂次
      • 接口实现类
        • Expr:表达式
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • Var:幂函数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • Constant:常数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
        • TtrigonometricFactor:三角函数
          • 判断相等方法:hashCode equal
          • 输出方法:toString
          • 子类:
            • Sin:正弦函数
              • normalize:标准化方法,将Sin中内容转化为统一格式(改变正负)
            • Cos:余弦函数
              • normalize:标准化方法,将Cos中内容转化为统一格式(改变正负)

(2)度量分析

从图中可以看出Poly类和PolySimplyfy的OCavg和WMC较高,Parser的WMC较高,PolyParser原因同第一次作业,PolySimplyfy原因为每次化简时需要遍历所有键值对,同时一直化简到无法化简为止。

(3)Hack情况

本次强测结果比较惨痛,根本原因在于课下测试不充分。

  • 己方bug分析:本次作业个人在强测和互测共出现3个bug
    • PolyequalX2方法出错:未考虑x^2前系数
    • -sin(x)**0 解析出错:未考虑三角函数本身负号
    • getContentPoly方法复杂度过高,需要多次调用mul等函数,导致三角函数嵌套过多是TLE
  • 对方bug分析:
    • int类型 解析求和函数的起始或结束常数
    • sin((-x*x))化简出错
    • sum中出现i**0出错

四、架构设计体验

在第一次作业时,个人采取的方法为传统的用栈解析表达式,结果中测没过(后来发现是正则表达式的问题)。于是又用递归下降方法重新做了一遍,结果三次作业都没有进行大规模重构。现在想想如果采取用栈解析表达式的方法会导致方法冗长,每加入新的运算符都要对多个函数进行改写,不符合面向对象设计思想

在第二次作业时遇到最大的问题就是如何表示数据(多项式),在分析第一次架构的表示数据方法使用了Hashmap<Hashmap<Factor, int>, BigInteger>对数据进行存储。同时,完成基本代码编写花费了近乎一天半的时间对代码进行小范围重构(例如提取函数、分解函数、提取父类、重命名等等)(这一过程中《重构:改善既有代码设计》一书对我启发颇深)

在第三次作业时由于第二次作业的代码基本上已经能满足需求,所以在简单对部分类进行解耦后,花费了更多时间在提高性能上,但是却疏于对更改后的代码进行测试,导致出现了细微但较为严重的BUG,应当进行深刻反省。

五、感想&小结

Unit1说实话个人做起来还是比较艰难的,主要原因有以下几点:

  • 对文法分析的不熟悉,花费了6~7个小时理解并实现了递归下降法
  • 对面向对象的思想不熟悉,导致一部分代码在一开始不符合面向对象的思想,花费了较多时间阅读《重构:改善既有代码设计》并进行重构
  • 对测试方法不熟悉,导致第三次作业出现了严重BUG

虽然艰难,不过在完成本单元的学习后还是收获满满:)

posted @ 2022-03-25 17:40  Blore-lzn  阅读(106)  评论(0编辑  收藏  举报