buaa-2024面向对象设计与构造OO第一单元多项式分析的总结

22373124-李长佳 学生 2024-03-22 03:17:34

wj老师镇楼

目录

  • 架构迭代
  • 第一次作业
  • 类图
  • 流程
  • 第二次作业
  • 类图
  • 迭代
  • 第三次作业
  • 类图
  • 度量
  • 代码规模
  • 复杂度
  • 迭代
  • 重构
  • 优化方案
  • bug分析
  • 互测debug策略
  • 心得体会
  • 未来方向

架构迭代

第一次作业

类图

img

流程

预处理
String.replaceAll方法去除字符串中所有空白符
分析词元
将表达式字符串拆解成+ - * ^ 常数 x ( )等词元
构造语法树
遍历词元,以+/-为分隔分析表达式中的项,以*为分隔分析项中的因子
分析因子时,先把前置的所有+/-读完确定正负号,再以第一个词元是常数,x或者左括号来确定因子类型
最终构建一颗树:其根结点是表达式,第二层结点是项,第三层是因子(若含表达式因子可继续向下展开)

伪代码如下:
Expr parserExpr() {
数组terms存放项term,数组ops存放项间加减符号op
用parseTerm方法分析第一个项并加入terms数组
while (表达式未结束且本词元是加减号) {
将本词元加入ops数组
用parseTerm方法分析下一个项并加入terms数组
}
返回一个以terms和ops数组为属性的Expr型对象
}

Term parseTerm {
数组factors存放因子factors
用parseFactor方法分析第一个因子并加入factors数组
while(项未结束且本词元是乘号){
用parseFactor方法分析下一个因子并加入factors数组
}
返回一个以factors数组为属性的Term型对象
}

Factor parseFactor {
如果该词元是常数,则返回一个继承Factor的Num型对象
如果该词元是变量,则返回一个继承Factor的Variable型对象
否则返回一个继承Factor的Expr型对象
}

计算语法树
将因子表示成一个hashmap<指数, 系数>,其中:
常数因子a添加键值对<0, a>,变量因子x^a添加键值对<a, 1>
将因子的hashmap做指数相加,系数相乘的运算,得到项的hashmap
将项的hashmap做指数不变,系数相加的运算,得到表达式的hashmap
同理,表达式因子递归可得,最终将整个表达式转化为一个hashmap
输出多项式
将计算语法树根节点表达式得到的hashmap按照格式输出成多项式

第二次作业

类图

img

迭代

预处理
在预处理时将函数代入表达式,具体做法是:

读入函数,用hashmap记录<函数名,函数>的键值对,其中函数Func类有两个属性,第一个是用Arraylist保存的形参列vars,第二个是函数表达式。在每一条自定义函数输入后:

  1. 记录函数名
  2. 按照顺序读入形参,存入vars中
  3. 把'='后的字符串记录为函数表达式
  4. 把函数func和函数名的键值对加入hashmap

将原表达式中的函数替代,具体做法是:

  1. 在原表达式中遍历到'f','g','h'
  2. 根据hashmap查询对应的函数func
  3. 将原表达式中函数名后括号中的实参按顺序读入实参数组params
  4. 将func的函数表达式中的x,y,z按照对应关系替换为实参(注意exp中的x也会被替换所以要先处理)
  5. 将替换后的函数表达式取代原表达式中的函数部分(注意将替换后的函数表达式两边加括号)

分析词元
只在type中添加了一种新枚举元素EXP
分析语法树
只在分析因子parseFactor时拓展了对指数函数Exp类的解析
计算语法树
在加入指数函数后,表达式最终每一项都会化简为形如a*x^b*exp(expr)的形式,因此,引入一种新的包装类Index,同时包含幂函数的指数index和指数函数的指数表达式powExpr,其中powExpr用hashmap<包装类Index, 系数>递归表示,对Index类设计两种构造器:

  1. 构造时只给出幂函数指数index,则powExpr部分设为null,形如计算变量因子或常数因子时调用
  2. 构造时给出幂函数的指数index和指数函数的指数表达式powExpr,形如计算指数函数因子时调用

因此,需要重写Calculator中对hashmap的加法addTable和乘法mulTable,以及需要对两个Index对象是否相等进行判定,除此之外,为了减少重复代码,还在Calculator类中抽象了去除hashmap系数为0的项的方法simTable,判等大致思路如下:

  1. 1和2的幂函数指数index是否相等,不相等返回false,相等进入下一步
  2. 1和2的指数函数指数表达式powExpr是否都为null(相当于exp(0)或者1),是则返回true,否则进入下一步
  3. 1和2的指数函数指数表达式powExpr是否其中一个为null,是则返回false,否则进入下一步
  4. 1和2的指数函数指数表达式powExpr是否键值对数目相等,否则返回false,是则进入下一步
  5. 用一个Arraylist保存在2的指数函数指数表达式powExpr中匹配过的键值Index,取1的指数函数指数表达式powExpr中的一个键值对3,再遍历2的指数函数指数表达式powExpr中的每一个键值对4,如果键值对4没有遍历过(即Arraylist中没有4的键值)且递归判别3和4相等,则将4的键值加入到Arraylist且break,遍历1中的下一个键值对。如果直到2中遍历完,都没有找到能匹配3的键值对,则返回false。如果将1完全遍历,则返回true
    5步伪代码如下:
    ArrayList<Index> traversal = new ArrayList<>(); // 在该数组中即为遍历过
    for (遍历1的键值3) {
      boolean everEqual = false; // 是否曾匹配过
      for (遍历2的键值4) {
          if (traversal不包含4 且 递归得到34的键值对相等)) {
              将4加入traversal
              everEqual = true;
              break;
          }
      }
      if (!everEqual) {
          return false;
      }
    }
    return true;
    

输出表达式
优化思路:

  1. 第一项尽量放系数为正的项,可以省一个'+'
  2. 1/-1在非第一项且非常数项中数字可省略
  3. 1次项即x^1 的^1可省略

第三次作业

类图

img

度量

代码规模
源文件总行数源代码行数源代码占比
Printer.java1221130.92
Calculator.java1131050.92
Parser.java108880.81
Lexer.java79710.89
Func.java75640.85
Index.java73640.87
Expr.java65560.86
Term.java44380.86
Exp.java37310.83
Dx.java34270.79
Var.java34280.82
Num.java29220.75
Main.java26200.76
Token.java19140.73
Factor.java1490.64
总计8727500.86
复杂度
方法CogCev(G)iv(G)v(G)
factor.Dx.calFactor()0.01.01.01.0
factor.Dx.derive()0.01.01.01.0
factor.Dx.Dx(boolean, Expr)0.01.01.01.0
factor.Exp.calFactor()1.01.02.02.0
factor.Exp.derive()0.01.01.01.0
factor.Exp.Exp(boolean, int, Factor)0.01.01.01.0
factor.Expr.calFactor()10.01.06.07.0
factor.Expr.derive()2.02.02.03.0
factor.Expr.Expr(boolean, ArrayList, int)0.01.01.01.0
factor.Expr.reverseOp(boolean)0.01.01.01.0
factor.Expr.setIndex(int)0.01.01.01.0
factor.Factor.Factor()0.01.01.01.0
factor.Num.calFactor()1.02.02.02.0
factor.Num.derive()0.01.01.01.0
factor.Num.negate()0.01.01.01.0
factor.Num.Num(String)0.01.01.01.0
factor.Term.calFactor()1.01.02.02.0
factor.Term.derive()6.01.04.04.0
factor.Term.Term(ArrayList)0.01.01.01.0
factor.Var.calFactor()1.01.02.02.0
factor.Var.derive()2.02.02.03.0
factor.Var.Var(boolean, int)0.01.01.01.0
Main.main(String[])1.01.02.02.0
output.Calculator.addTable(HashMap<Index, BigInteger>, HashMap<Index, BigInteger>)18.06.08.013.0
output.Calculator.Calculator()0.01.01.01.0
output.Calculator.deriveExpr(HashMap<Index, BigInteger>)3.01.03.03.0
output.Calculator.deriveTerm(Index, BigInteger)3.02.03.04.0
output.Calculator.mulTable(HashMap<Index, BigInteger>, HashMap<Index, BigInteger>)15.06.06.08.0
output.Calculator.simTable(HashMap<Index, BigInteger>)5.03.03.05.0
output.Index.equal(Index, Index, BigInteger, BigInteger)30.011.09.016.0
output.Index.getIndex()0.01.01.01.0
output.Index.getTable()0.01.01.01.0
output.Index.Index(BigInteger)0.01.01.01.0
output.Index.Index(BigInteger, HashMap<Index, BigInteger>)0.01.01.01.0
output.Printer.getPrinter()1.01.01.02.0
output.Printer.Printer()0.01.01.01.0
output.Printer.printExp(HashMap<Index, BigInteger>)43.09.015.018.0
output.Printer.printExpr(HashMap<Index, BigInteger>)7.04.06.07.0
output.Printer.printTerm(boolean, Index, BigInteger)13.01.010.010.0
parser.Func.addFunc(String)1.01.02.02.0
parser.Func.Func(ArrayList, String)0.01.01.01.0
parser.Func.funcToString(String)27.01.010.012.0
parser.Lexer.getLexer()1.01.01.02.0
parser.Lexer.lex(String)20.013.012.014.0
parser.Lexer.Lexer()0.01.01.01.0
parser.Lexer.move()0.01.01.01.0
parser.Lexer.notEnd()0.01.01.01.0
parser.Lexer.now()0.01.01.01.0
parser.Parser.getIndex()4.01.04.04.0
parser.Parser.getParser(Lexer)1.01.01.02.0
parser.Parser.parse(String)0.01.01.01.0
parser.Parser.parseExpr()3.01.04.04.0
parser.Parser.parseFactor()10.05.08.08.0
parser.Parser.Parser(Lexer)0.01.01.01.0
parser.Parser.parseTerm()2.01.03.03.0
parser.Token.getContent()0.01.01.01.0
parser.Token.getType()0.01.01.01.0
parser.Token.Token(Type, String)0.01.01.01.0
Total232.0111.0163.0194.0
Average4.01.92.83.3

迭代

  1. 将求导因子包装成Dx类
  2. 将求导功能从因子类继承到每个子类中
  3. 在Calculator类中新增了对已经转化为Hashmap形式的表达式的求导,采用向下递归的方法

重构

  1. 将对因子的计算calFactor,calExpr等方法从Calculator类下放到每个因子类中
  2. 将因子类,输出类,解析类包装成packet,结构清晰
  3. 去除了Expr类中的Ops数组,不再记录内部每一项term前面的符号,默认每一项term前为加号,将那些重复的加减号变成因子factor的属性
  4. 抽象解析过程中的取指方法,减少代码重复
  5. 将打印类分为输出表达式和输出项,对项的输出逻辑做了优化,对指数函数的表达式是否带括号做了性能分优化

优化方案

  1. 若指数函数的表达式部分只有一项且该项为单个因子时,可以特判不用加双重括号,如:

    exp((1)) -> exp(1)
    exp((x)) -> exp(x)

  2. 若指数函数的表达式部分只有一项且为系数为大于0的变量因子(如x^5或exp)时,可以把系数提到括号外,如:

    exp((5*x^3)) -> exp(x^3)^5
    exp((3*exp(3))) -> exp(exp(3))^3

  3. 若指数函数的表达式部分有多项时,可以尝试提公因数,为了防止TLE,只特判系数中最小的数是否是公因数即可,并且提公因数后的式子长度必须小于提公因数前的长度才行,如:

    exp((2*x^2+2*x^5+8*x^7)) -> exp((x^2+x^5+4*x^7))^2

bug分析

出错样例
0
dx(-dx(x^3+x^2)*x)
分析
dx(expr)没有问题
dx(dx(expr))没有问题
dx(dx(expr)*x)没有问题
dx(-dx(expr)*x)有问题
原因
dx(expr)会对expr求导,如果expr是-dx(expr1)*(expr2)的形式,会对dx对象先后使用derive和calfactor方法
在dx对象的derive和calFactor方法中,会通过改变dx对象的表达式的正负号来把dx对象的正负号代入到计算中
若dx对象的isPositive属性为false,则expr1的符号会被改变两次,则第二次改变后expr1的符号和原式相比没有变化,导致结果正负号发生错误
解决办法
在使用derive后将expr1的符号修改回来
经验教训
命令-查询分离原则——设计接口时,修改对象字段的方法无返回值,有返回值的方法不修改对象的字段

互测debug策略

  1. 使用评测机
  2. 设计容易TLE的点
  3. 根据各个部分的功能编写数据检验正确性,如

    在预处理时将函数代入表达式时有没有加括号,如:
    f(x,y) = x+y
    f(1,2)*x
    如果代入函数时没有加括号,就会变成1+2*x,结果错误

心得体会

  1. 要按时交作业,不要抱有侥幸心理,不要觉得自己能当一天做完作业的超人
  2. 要对数据充分测试,有评测机最好
  3. 要注意代码结构设计,保证开闭原则,逻辑清晰美观,要充分利用java语言的特点:面向对象、封装、继承和多态性

未来方向

  1. 第一次作业到第二次作业难度跨度稍大,且无上机课辅助,而第二次作业到第三次作业跨度又很小,可以适当将第二次作业内容部分转移到第三次作业内容
  2. 建议把性能分下调到占比10分左右
  3. 建议以后的课设坚持指数函数化简这种没有最优解的性能分优化手段的问题,也保证了一定的区分度

谢谢助教支持以及课程组的精心设计,受益匪浅😸

...全文
88 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧