面向对象设计与构造_第一单元总结

杨馨悦-22182610 2024-03-21 19:27:56

第一次作业

程序架构

img

最初的设想是用Expr->Term->Factor的架构,但是由于刚刚接触Java,还不懂接口等方法,不会处理表达式因子,所以直接把Factor看成了一个项,每个Factor都是a*x^n的形式。

我在Factor里分别创建了symbol指示这一项的正负号,index指示幂次,variable指示变量(在本单元作业中是只有x,当时考虑可拓展性加了variable,在第二次作业的迭代中删了),constant指示系数

于是,在这样的的架构下,最终输出的就是多个Factor相加的形式,也就是Expr。这样一来Term的设计就显得很多余,实际上Term也确实在输出时没有起到作用,Term在我的架构里很像辅助Expr乘法计算的一个中间量,我们把Term也看作是一个表达式,每读取一个Term就进行一次Term*Term,处理完这一级全部的乘法之后就返回上一级,在返回前,Term已经是多个Factor相加的表达式,把Term里的所有Factor都加到Expr里,再合并同类项就可以完成拆括号。(实际上可以给TermFactor换个名字,会方便理解一点)

Processing

完成了字符串的预处理和后处理

预处理:
  1. 删除空格和制表符
  2. 删除前导零
  3. 将连续的加减号(最多三个)替换为单个加减号
  4. *+^+(++删除
  5. 在每个左括号前添加 1*,确保括号前有乘法因子
  6. 如果字符串以加号开始,则移除它
后处理(在toString中的处理不太好,所以做了后处理)
  1. 将连续的加减号替换为单个加减号
  2. 移除+0的项

Lexer

沿用了training中的Lexer模块,进行字符串解析,解析出 数字字符串 字母字符串 符号字符串

Parser

采用了递归下降的写法,其架构如下

public Expr parseExpr() {
    // ...parseExpr
    while (lexer.peek().equals("+") || lexer.peek().equals("-")) {
        // ...parseExpr
    }
    return expr;
}

public Term parseTerm() {
    // ...parseFactor
    while (lexer.peek().equals("*")) {
        // ...parseFactor
    }
    return term;
}

public Term parseFactor() {
    if (lexer.peek().equals("(")) {
        ...... // 读取表达式因子
    }
    else if (lexer.peek().equals("x")) {
        ...... // 读取变量因子
    }
    else if (lexer.peek().matches("\\d+")) {
        ...... // 读取常数因子
    }
    return term; 
}

需要说明的是,这里parseFactor的返回值是Term类。这是由于我在这里进行了表达式因子的幂运算,返回的是ArrayList<Factor>,我又不希望再在Factor里加入更繁琐的ExprTerm,所以如果处理的是变量因子常数因子,返回的Term size==1,这样的设计可以使结构更加简单。

Expr

表达式类

  • 包含一个属性ArrayList<Factor>

  • 实现了addTermaddFactorsimplifyExpr的方法

addTerm:把Term中所有的Factor都通过调用addFactor加到Expr

addFactor:把Factor加到Expr

simplifyExpr:化简表达式(合并同类项),比较每个Factorx的幂次是否相同,相同的就系数相加,不同的就添加一个新的Factor

Term

乘法计算的表达式

  • 包含一个属性ArrayList<Factor>
  • 实现了multiplypowaddAll的方法

multiply:乘法运算,计算表达式Term和表达式Term相乘得到的新表达式

pow:计算Term自己的幂次运算,即将乘法运算进行index次

addAll:把parseFactor得到的Term中的所有Factor,全都加到当前的Term

Factor

  • 包含四个属性,symbol:这一项的正负号,index:幂次,variable:变量,constant:系数

  • 主要实现了mergeFactor的方法

mergeFactor:把两个Factor合并,系数相加,幂次不变,符号和计算后的系数相同。

复杂度分析

MethodCogCev(G)iv(G)v(G)
expr.Expr.addFactor(Factor)0.01.01.01.0
expr.Expr.addTerm(Term)0.01.01.01.0
expr.Expr.Expr()0.01.01.01.0
expr.Expr.getFactors()0.01.01.01.0
expr.Expr.simplifyExpr(Expr)4.01.03.03.0
expr.Expr.toString()4.02.03.03.0
expr.Factor.Factor(int, int, String, BigInteger)0.01.01.01.0
expr.Factor.getConstant()0.01.01.01.0
expr.Factor.getIndex()0.01.01.01.0
expr.Factor.getSymbol()0.01.01.01.0
expr.Factor.getVariable()0.01.01.01.0
expr.Factor.mergeFactor(Factor)3.01.02.03.0
expr.Factor.toString()17.02.010.011.0
expr.Term.add(Factor)0.01.01.01.0
expr.Term.addAll(Term)0.01.01.01.0
expr.Term.addFactor(Factor)0.01.01.01.0
expr.Term.getFactors()0.01.01.01.0
expr.Term.multiply(Term)7.01.04.05.0
expr.Term.pow(Expr, Integer)6.01.06.06.0
expr.Term.size()0.01.01.01.0
expr.Term.Term()0.01.01.01.0
expr.Term.toString()4.02.03.03.0
Lexer.getConstant()2.01.03.03.0
Lexer.getVariable()2.01.03.03.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.next()4.02.04.05.0
Lexer.peek()0.01.01.01.0
MainClass.main(String[])0.01.01.01.0
Parser.parseExpr()5.01.05.05.0
Parser.parseFactor()10.02.08.08.0
Parser.Parser(Lexer)0.01.01.01.0
Parser.parseTerm()1.01.02.02.0
Processing.postProcess(String)0.01.01.01.0
Processing.preProcess(String)1.01.02.02.0
Total70.039.078.082.0
Average2.05882352941176451.14705882352941172.29411764705882342.411764705882353

本次作业的架构比较简单,只有expr.Factor.toString()方法的复杂度较高,这是因为我在这个方法里做了较多的判断逻辑,如幂次为0时x不输出 系数输出。我在输出0 输出1的时候都做了一些特判,是因为我担心自己在其他地方的赋值不够规范,但其实有一部分判断是冗余的

ClassOCavgOCmaxWMC
expr.Expr1.66666666666666673.010.0
expr.Factor2.42857142857142849.017.0
expr.Term2.1111111111111116.019.0
Lexer2.25.011.0
MainClass1.01.01.0
Parser3.758.015.0
Processing1.52.03.0
Total76.0
Average2.2352941176470594.85714285714285710.857142857142858

其中Parser类的复杂度较高,主要是由于解析表达式时有较多的判断逻辑,判断因子是哪个因子等

img

本次作业的码量不算大,主要是架构的设计比较花费时间。以及我没选OOP,花了很多时间来熟悉和读懂Java,wwwwww...

Bug分析

  • 本次作业中,我的程序在强测和互测中都没有出现bug

  • 我在互测环节hack了房内的两名同学,测试点分别是(1)*-7(该同学的程序会输出7)和x+(x^3+x)^6

    • 我借用了往届学长在GitHub开源的评测机Toby-Shi-cloud/OOAutoTest:,经过修改后使之符合本次作业要求
    • 通过自动打包,自动生成数据以及借用python sympy包自动评测的方式,测试自己的程序和房内同学的程序

性能优化分析

在整个单元我都没有在性能优化上做太多的实现,主要是担心性能优化考虑的不到位导致强测过不了,或者是性能优化导致程序可拓展性降低,不利于后续的迭代。

在第一周作业中,我把+0的项都删掉了,同时,由于我的架构天然的会把x*x*...*x转化成x^n故而省略了这一步的优化

由于时间紧促,我也并没有把+项先输出,-项后输出,来省略一个+号,这部分优化我在第二次作业中补上了。

第二次作业

程序架构

img

在本次作业中新增了括号嵌套、自定义函数和exp函数,其中括号嵌套在递归下降的架构中天然适用,不需要考虑

对于自定义函数,我在Lexer里新增了fgh的识别,在Parser里新增了parseSeDeFuction方法,解析自定义函数。我新建了Function类,用于在读入自定义函数时替换xyz,以及在读入表达式时替换自定义函数。

对于exp函数,我在Factor中新增了exp属性,使每个项都以a*x^n*exp(...)的形式存在,我把exp的次方都乘到括号内了,使架构简单。

架构迭代

Processing

我删除了后处理,把0项不输出和加减号放在了toString里进行了处理

由于有新增函数,所以我在预处理部分做了一点更改

预处理:
  1. 删除空格和制表符
  2. 删除前导零
  3. 将连续的加减号(最多三个)替换为单个加减号
  4. *+^+(++删除
  5. 将表达式中的exp替换为1*e
  6. 在每个左括号前添加 1*
  7. 将以 f1*(, g1*(, h1*(, e1*( 开头的字符串替换为 f(, g(, h(, e(,以消除多余的数字 1
  8. 如果字符串以加号开始,则移除它
FuncProcess

用于在读取表达式时,将自定义表达式逐层替换(通过递归,先替换最里层的)

Function

  • 新增的类,包含一个属性HashMap<String,String>,用来存储自定义函数,其中key是f/g/h,value是该自定义函数对应的表达式(我把xyz分别替换成了uvw,防止x的替换出现问题)

  • 实现了addFunction getFunctions funcToString方法

addFunction:每读到一个新的自定义函数,就把这个自定义函数加入到Function(HashMap)里

getFunctions:取出所有的Function

funcToString:把表达式中的自定义函数替换成带入xyz后的字符串

Expr

表达式类

  • 新增了deepCopysortFactorspowExp的方法

deepCopy:深拷贝

sortFactors:对Factor排序,+项在前

powExp:exp幂运算,把幂次和exp中的每个项相乘

Factor

  • 包含四个属性,symbol:这一项的正负号,index:幂次,constant:系数,exp:指数函数

  • 实现了deepCopy的方法

  • 修改了mergeFactortoString,加入了对exp的合并和输出

复杂度分析

MethodCogCev(G)iv(G)v(G)
expr.Expr.addAllExpr(Expr)0.01.01.01.0
expr.Expr.addFactor(Factor)0.01.01.01.0
expr.Expr.addTerm(Term)0.01.01.01.0
expr.Expr.deepCopy()1.01.02.02.0
expr.Expr.equals(Expr)8.04.06.07.0
expr.Expr.Expr()0.01.01.01.0
expr.Expr.getFactors()0.01.01.01.0
expr.Expr.isSingleFactor()8.01.010.011.0
expr.Expr.powExp(Expr, Integer)1.01.02.02.0
expr.Expr.simplifyExpr(Expr)9.04.06.06.0
expr.Expr.size()0.01.01.01.0
expr.Expr.sortFactors()0.01.01.01.0
expr.Expr.toString()18.02.07.07.0
expr.Factor.deepCopy()1.01.02.02.0
expr.Factor.Factor(int, BigInteger, BigInteger, Expr)0.01.01.01.0
expr.Factor.getConstant()0.01.01.01.0
expr.Factor.getExp()0.01.01.01.0
expr.Factor.getIndexX()0.01.01.01.0
expr.Factor.getSymbol()0.01.01.01.0
expr.Factor.mergeFactor(Factor)3.01.02.03.0
expr.Factor.toString()26.02.017.018.0
expr.Term.add(Factor)0.01.01.01.0
expr.Term.addAll(Term)0.01.01.01.0
expr.Term.addFactor(Factor)0.01.01.01.0
expr.Term.getFactors()0.01.01.01.0
expr.Term.multiply(Term)6.01.04.04.0
expr.Term.pow(Expr, Integer)6.01.06.06.0
expr.Term.simplifyTerm(Term)9.04.06.06.0
expr.Term.size()0.01.01.01.0
expr.Term.Term()0.01.01.01.0
expr.Term.toString()4.02.03.03.0
Function.addFunction(String, String)0.01.01.01.0
Function.Function()0.01.01.01.0
Function.funcToString(String, String, int, String)3.02.02.05.0
Function.getFunctions()0.01.01.01.0
Lexer.getConstant()2.01.03.03.0
Lexer.getVariable()2.01.03.03.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.next()7.02.06.08.0
Lexer.peek()0.01.01.01.0
MainClass.main(String[])1.01.02.02.0
Parser.parseExpr()5.01.05.05.0
Parser.parseFactor()15.03.011.012.0
Parser.Parser(Lexer)0.01.01.01.0
Parser.parseSeDeFunction(Function)16.06.08.08.0
Parser.parseTerm()1.01.02.02.0
Processing.funcProcess(String)30.06.011.011.0
Processing.preProcess(String)1.01.02.02.0
Total183.074.0152.0162.0
Average3.81251.54166666666666673.16666666666666653.375

Processing.funcProcess(String)expr.Factor.toString()的复杂度较高

主要是因为funcProcess里有for循环和递归的嵌套,复杂度较高,Factor.toString中在上次作业的基础上加入了对exp的判断,导致复杂度更高了

ClassOCavgOCmaxWMC
expr.Expr<2.4615384615384617,2.4615384615384617><7.0,7.0><32.0,32.0>
expr.Factor<2.875,2.875><13.0,13.0><23.0,23.0>
expr.Term<2.4,2.4><6.0,6.0><24.0,24.0>
Function<2.25,2.25><6.0,6.0><9.0,9.0>
Lexer<2.6,2.6><7.0,7.0><13.0,13.0>
MainClass<2.0,2.0><2.0,2.0><2.0,2.0>
Parser<5.2,5.2><11.0,11.0><26.0,26.0>
Processing<5.0,5.0><8.0,8.0><10.0,10.0>
Total<139.0,139.0>
Average<2.8958333333333335,2.8958333333333335><7.5,7.5><17.375,17.375>

其中Parser类和Processing类的复杂度较高,主要是由于解析表达式时有较多的判断逻辑,预处理自定义函数的时候有较多的递归替换

img

代码增量大概在300行以内,在自定义函数的递归替换部分我花了较多的时间

Bug分析

  • 本次作业中,我在强测中出现了致命的bug导致没有进互测房(哭)

  • 我的bug主要都出在了字符串预处理的部分,第二周把全部的精力都放在了exp上,没有在字符串预处理部分做过多的思考。

    一共修复了3个bug

    1. 修复了-(的问题,将其转换成了-1*(,使之对于我的设计来说是可读的
    2. 修复了自定义函数中无法读入空格的问题,将所有的空格删除,之前程序会因为无法继续读入而超时
    3. 修复了(0+0+……+)^5计算时多个0相乘造成的内存爆炸问题,将其优化为(0)^5

性能优化分析

首先是修改了第一周残留的性能问题。把字符串后处理拿掉了,在expr.Expr.toString()expr.Factor.toString()中进行处理加减号和0的输出问题;在化简Expr的同时,加入一个sort方法,以symbol来sort,使正号的项排序在前,先输出。

本周作业其实是有较多的性能优化可以做的,但我只完成了exp中为单个因子时,输出一层括号的优化。此外,我也没有对exp(0)=1进行优化,也没有提取公因子 以exp(...)^n的形式输出。

第三次作业

程序架构

img

在本次作业中新增了在自定义函数调用自定义函数和求导的功能

对于在自定义函数调用自定义函数,我在main里每次读取新的自定义函数表达式时,调用一次表达式的自定义函数预处理方法Processing.funcProcess

对于求导,我在Factor中新增了deriveFactor方法,对每个项a*x^n*exp(...)求导。我在Expr中新增了deriveExpr方法,对表达式求导。

架构迭代

Expr

表达式类

  • 新增了deriveExpr方法

deriveExpr:把expr中的每一项factor求导后得到的term,都加到记录求导结果的expr中

Factor

  • 新增了deriveFactor方法

deriveFactor:对每个a*x^n*exp(...)运用求导法则进行运算,得到每个factor的求导结果a*n*x^(n-1)*exp(...)+a*x^n*exp(...)*deriveExpr(...)

复杂度分析

MethodCogCev(G)iv(G)v(G)
expr.Expr.addAllExpr(Expr)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.addFactor(Factor)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.addTerm(Term)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.deepCopy()<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
expr.Expr.deriveExpr()<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
expr.Expr.equals(Expr)<8.0,8.0><4.0,4.0><6.0,6.0><7.0,7.0>
expr.Expr.Expr()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.getFactors()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.isSingleFactor()<8.0,8.0><1.0,1.0><10.0,10.0><11.0,11.0>
expr.Expr.powExp(Expr, Integer)<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
expr.Expr.simplifyExpr(Expr)<9.0,9.0><4.0,4.0><6.0,6.0><6.0,6.0>
expr.Expr.size()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.sortFactors()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Expr.toString()<18.0,18.0><2.0,2.0><7.0,7.0><7.0,7.0>
expr.Factor.deepCopy()<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
expr.Factor.deriveFactor()<7.0,7.0><3.0,3.0><6.0,6.0><6.0,6.0>
expr.Factor.Factor(int, BigInteger, BigInteger, Expr)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Factor.getConstant()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Factor.getExp()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Factor.getIndexX()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Factor.getSymbol()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Factor.mergeFactor(Factor)<3.0,3.0><1.0,1.0><2.0,2.0><3.0,3.0>
expr.Factor.toString()<26.0,26.0><2.0,2.0><17.0,17.0><18.0,18.0>
expr.Term.add(Factor)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.addAll(Term)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.addFactor(Factor)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.getFactors()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.multiply(Term)<6.0,6.0><1.0,1.0><4.0,4.0><4.0,4.0>
expr.Term.pow(Expr, Integer)<6.0,6.0><1.0,1.0><6.0,6.0><6.0,6.0>
expr.Term.simplifyTerm(Term)<9.0,9.0><4.0,4.0><6.0,6.0><6.0,6.0>
expr.Term.size()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.Term()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
expr.Term.toString()<4.0,4.0><2.0,2.0><3.0,3.0><3.0,3.0>
Function.addFunction(String, String)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Function.Function()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Function.funcToString(String, String, int, String)<3.0,3.0><2.0,2.0><2.0,2.0><5.0,5.0>
Function.getFunctions()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Lexer.getConstant()<2.0,2.0><1.0,1.0><3.0,3.0><3.0,3.0>
Lexer.getVariable()<2.0,2.0><1.0,1.0><3.0,3.0><3.0,3.0>
Lexer.Lexer(String)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Lexer.next()<7.0,7.0><2.0,2.0><6.0,6.0><8.0,8.0>
Lexer.peek()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
MainClass.main(String[])<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
Parser.parseDerive()<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Parser.parseExp()<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
Parser.parseExpr()<5.0,5.0><1.0,1.0><5.0,5.0><5.0,5.0>
Parser.parseFactor()<14.0,14.0><4.0,4.0><11.0,11.0><12.0,12.0>
Parser.Parser(Lexer)<0.0,0.0><1.0,1.0><1.0,1.0><1.0,1.0>
Parser.parseSeDeFunction(Function)<16.0,16.0><6.0,6.0><8.0,8.0><8.0,8.0>
Parser.parseTerm()<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
Processing.funcProcess(String)<31.0,31.0><6.0,6.0><12.0,12.0><12.0,12.0>
Processing.preProcess(String)<1.0,1.0><1.0,1.0><2.0,2.0><2.0,2.0>
Total<192.0,192.0><81.0,81.0><164.0,164.0><174.0,174.0>
Average<3.6923076923076925,3.6923076923076925><1.5576923076923077,1.5576923076923077><3.1538461538461537,3.1538461538461537><3.3461538461538463,3.3461538461538463>

新增方法的复杂度都不高

img

代码增量只有不到100行,本次作业对我来说相对轻松,之前的架构能很好地完成本次作业迭代

Bug分析

  • 本次作业中,我在自测时发现了一个bug

    2
    f(y,x)=y^2
    g(y,x)=f(x,y)
    g(1,5)
    

    应该输出25,但我的程序输出了1。这是因为在自定义函数字符串替换的时候,会把替换后的uvw 看作是还没有被替换的东西,进行了重复的替换。

    解决方法:把每个形参用不同的值进行存储,不要统一使用uvw。我的解决方法是用fu fv fw gu gv...来代替uvw,从而保证不会有重复替换的情况出现。

  • 在强测中,我成功hack了房间里的一名同学

    2
    f(y,x,z)=1
    g(z)=f(1,1,1)
    f(1,1,g(1))
    

    该同学的程序会抛出异常

    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
        at FuncMap.submit0(FuncMap.java:96)
        at FuncMap.submit(FuncMap.java:41)
        at Main.main(Main.java:29)
    

第一单元作业总结

由于是第一次接触Java,我的架构设计的并不是很理想,在最开始的一两周不是很理解什么是接口,继承这些关系,也一直不懂为什么同学们的架构中都加入了PolyMono这两个类。直到这周的研讨课,听了同学对自己架构的分享我才理解。我确实认为加入了PolyMono的结构是更合适的,从架构上也更符合自下而上的思想,读写分离使得操作更加方便清晰。

而我的架构的弊端也显而易见

  1. 用看似是expr term factor的命名,实际他们可能是以poly tempPoly Mono的形式存在的,感觉程序的架构不是很容易表述清楚,很容易让别人误解。

  2. 在Factor中没有使用接口,我认为程序的可拓展性是大幅下降的。同时,为了能让数据在Expr Term Factor中传输,我多写了很多方法,但如果使用接口就不需要这么麻烦了。

  3. 相比加入了PolyMono的架构而言,我的架构不够清晰,可扩展性也比较低。

如果能再来一次,能不能让我在上个学期就知道有oop这门课啊(哭)

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

301

社区成员

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

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