第一单元OO作业

第一单元OO作业

本单元作业的主题是多项式展开与化简,既要将表达式进行展开,以去除不必要的括号,也要将展开的表达式进行合并,得到更精炼的输出以及更高的性能分。

第一次作业

本次作业需要完成的任务为:读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式.

总体思路与架构设计

在第一次作业中,我使用了递归下降的方法,通过Lexer类将输入进行处理,并将处理结果交给Parser类,Parser类将顺序结构的内容,建成Expr -> Term -> Factor -> Expr -> ··· 的树状递归结构,再递归调用函数的toPoly()方法,借助PolyMono两个类,将节点上形式不同的表达式树,建立为统一的表达方式。

具体细节

预处理

为了便于后续的处理,在Lexer类的初始化中调用pre()方法,将输入的字符串input中的空白符删去,将连续的加减符号化为单一的符号。同时,考虑到表达式最开始会存在单独的-+符号,所以在所有的表达式开始都添加了一个0(但此操作也为后面操作带来一些问题如dx())。处理完后,便可以使用Parser类进行递归下降了。

    private void pre() {
        input = input.replaceAll(" ", "");
        input = input.replaceAll("\t", "");
        input = input.replaceAll("\\^\\+", "^");
        input = input.replaceAll("\\++", "+");
        input = input.replaceAll("\\+-", "-");
        input = input.replaceAll("-\\+", "-");
        input = input.replaceAll("--", "+");
        input = input.replaceAll("---", "-");
        input = input.replaceAll("\\++", "+");
        input = input.replaceAll("\\(\\+", "(0+");
        input = input.replaceAll("\\(-", "(0-");
        if (input.charAt(0) == '+' || input.charAt(0) == '-') {
            input = "0" + input;
        }
    }

递归下降
思想

递归下降,其实就是逻辑上一棵树的构建过程,并且将其按照不同的层次进行分拆的方法。

之前的数据结构课程中,进行表达式计算的时候,是采取两个栈对字符串进行处理,转换成后缀表达式,进行计算。而递归下降则是通过分析表达式的结构,将其从逻辑上分为表达式、项、因子这三种,只需要保证顺序调用即可。对于表达式来说,它只需要完成自己的方法,即将识别到的项加入到自己的terms中。而term如何识别,如何保证识别完term后,下一个仍是符合term表达形式的字符串,则是parseTerm()方法的定义。这便是面向对象中的封闭性,从两种课程中对于大致相同的表达式计算而采取的不同思路方式,便是面向过程与面向对象的区别。

实现

在第一次作业中,写了三个方法,分别是parseExpr()parseTerm(int sign)parseFactor(),内部结构都大致相同。但是由于每个项都有符号,所以在进入parseTerm()时,将符号传入作为内部项的符号,从而外部表达式只需进行加法运算。parseFactor()内部多了几条判断,来进行不同因子的识别,而根据形式化要求,部分因子会有指数,因此抽象出一个indexGet()方法来获取此值。至于为什么时index而不是exp,因为他们都是`指数`

    public Expr parseExpr() {
        Expr expr = new Expr();
        expr.addTerm(parseTerm(1));
        int sign;
        while ("+-".contains(lexer.peek())) {
            if (lexer.peek().equals("+")) {
                sign = 1;
            } else {
                sign = -1;
            }
            lexer.next();
            expr.addTerm(parseTerm(sign));
        }
        return expr;
    }

计算方法

经过递归下降的处理后,顺序的数据串,已经变成了有表达式、项、因子作为节点的树状结构。由于前三种抽象层次在计算方面的共性并不多,因此新建了两个类PolyMono,其中,Mono类中,定义了运算时基本项的形式,即a*x^bPoly则是Mono的集合。这两个类中主要内容为实现计算的方法。

那么接下来的工作,便是将原来的结构变成新的类中的对象。Var类和Const类中比较简单,只需要将对应值填入即可。

//Var.java
    public Poly toPoly() {
        Poly poly = new Poly();
        poly.monoAdd(new Mono(new BigInteger("1"), index));
        return poly;
    }   

其中monoAdd()这一方法的实现需要注意,在加入一个新的mono时,就进行合并,判断两个基本项是否可加,即x的指数是否相同。这使得在多项式之间的加减时变的简单。

而Term类中便相对复杂一些,它需要将每个的factor的多项式进行相乘。Expr类中的也类似,只是将乘变成了加。

//Term.java
    public Poly toPoly() {
        Poly poly = new Poly();
        for (Factor factor : this.factors) {
            poly = poly.multiPoly(factor.toPoly());
        }
        if (this.sign == -1) {
            Const neg = new Const(new BigInteger("-1"));
            poly = poly.multiPoly(neg.toPoly());
        }
        return poly;
    }

multiPoly()则是将两个多项式逐项相乘,成为新的多项式。

输出

由于在toPoly()中就已经将其化简合并,输出只需要调用,PolyMono的重写的toString()方法。而PolytoString只需要遍历调用所有的mono基本项的toString进行拼接即可。

而Mono的toString也只需要进行不同情况的判断处理,注意好细节点,没有思维上的难度。

优化

合并同类项

如前文所说,在进行加减乘计算后,写入结果时,先判断结果中是否存在可合并的项,如果没有,再加入新的项。

调整输出
  • 负数在第一位比正数在第一位会多出一个字符,因此我重写了monocompareTo(),以实现arraylist的排序。

  • 部分合理省略,在输出的过程中加入很多条件判断

    • -1*x^1 => -x

    • +32 => 32

    • x^0 => 1

bug分析

MemoryOverFlow

由于在计算时尝试全部拆开再合并,导致中间过程中MLE了。朴实无华的bug。解决措施也同样朴实,每次多项式的加减进行计算后,调用merge进行合并一下就ok了。

java中的内存,只要你失去了对他的指针,那么jvm就会帮你自动回收,不需要进行自己进行什么操作。

而且评测不会刻意卡memory的,只要不是全展开再合并或者说陷入了死循环,一般没有这个问题。

CurrentModificationException

使用ArrayList()进行数据的存储时,可能经常出现CurrentModificationException(这个bug甚至一度让我觉得以后再也不用ArrayList()了),一般来说,出现这个问题,表示在arraylist的遍历过程中,通过一些其他的方法,对arraylist中的元素进行了修改,常见的有以下两种原因。

深浅拷贝

因为浅拷贝的两个索引指向的是同一对象,因此很容易在遍历过程中因为修改了不应该修改的内容,导致出现错误。

只有八种基本类型的=是深拷贝,其余的深拷贝均需自行实现。

调试与toString()

在意识到这个问题之前,我一直不知道调试的信息时如何产生在屏幕上的,感觉是理所应当的事情。但是在了解了之后,却觉得一切合乎情理。

在调试时,变量会调用toString方法将将其内部值进行组织并打印出来,也就是说,如果你在toString中修改了变量的值,那么在调试过程中进行遍历arraylist就会出现CurrentModificationException错误!!

解决措施
  • 使用简单循环而非加强循环,但是这一操作只能解决互相修改导致的报错,但是仍会导致修改不该修改的,而导致更为隐藏的bug。

for (int i = 0; i < arraylist.size(); i++) {//替换for (Object object : arraylist){}
    Object object = arraylist.get(i);
    //...
}
  • 如果分不清楚某种情况下是否该用深拷贝,那就先使用深拷贝。

  • 不使用toString而使用新定义的print等函数。

方法度量

Method Metrics
Complexity metrics周四21 3月 2024 16:52:09 CST
MethodCogCev(G)iv(G)v(G)
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()42310
Lexer.peek()0111
Lexer.pre()2123
MainClass.main(String[])0111
Parser.Parser(Lexer)0111
Parser.indexGet()1122
Parser.parseExpr()5134
Parser.parseFactor()13367
Parser.parseTerm(int)1122
expr.Const.Const(BigInteger)0111
expr.Const.setIndex(int)0111
expr.Const.toPoly()1122
expr.Expr.Expr()0111
expr.Expr.addTerm(Term)0111
expr.Expr.setIndex(int)0111
expr.Expr.toPoly()2133
expr.Mono.Mono(BigInteger, int)0111
expr.Mono.addMono(Mono)1122
expr.Mono.compareTo(Mono)0111
expr.Mono.getCeo()0111
expr.Mono.getIndex()0111
expr.Mono.multiMono(Mono)0111
expr.Mono.toString()14288
expr.Poly.Poly()0111
expr.Poly.addPoly(Poly)8455
expr.Poly.merge()0111
expr.Poly.monoAdd(Mono)3333
expr.Poly.multiPoly(Poly)5345
expr.Poly.sort()0111
expr.Poly.toString()3234
expr.Term.Term(int)0111
expr.Term.addFactor(Factor)0111
expr.Term.toPoly()2133
expr.Var.Var(int)0111
expr.Var.toPoly()0111
具体分析

第一次作业较为简单,用插件检查后并没有被标红的项,其中toString中由于分支判断情况过多,因此略高。

parseFactor方法过度耦合,在第二次作业中,将其拆开解耦,降低复杂度。

lexer.next()条件过多,将字符逐个匹配修改为字符串中是否包含,减少条件判断的逻辑关系。

第二次作业

本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用的表达式,输出恒等变形展开所有括号后的表达式。

总体思路与架构设计

本次作业新增了嵌套多层括号,指数函数因子,自定义函数因子。因此在上一次的架构进行了一些修改

  • 增加Definer类与Fun类来处理自定义函数。

    • 考虑到可能会在多个地方调用自定义函数解析,为了减少传参的麻烦,将Definer设置为static类。

    • Fun实现了Factor的接口,报存函数表达式的具体内容以及形参,通过传入实参,处理后返回表达式的字符串。

    • Definer处理函数表达式,建立Fun对象,将实参传给Fun,并解析得到的字符串,返回Expr

  • 增加Euler类来保存exp指数,实现Factor接口。

  • 修改ConstNum,因为const为关键字,避免问题。

  • 由于递归下降方法的使用,括号嵌套可以直接实现。

具体细节

自定义函数
识别与存储

在输入阶段,首先将Scanner.in交给Definer,有Definer来进行数据的解析。解析时采取正则匹配的方式([fgh])\\(([,uvw]*)\\)=([\\w()+\\-*^]+)来进行处理,识别出形参与函数的表达式,新建一个Fun对象来存储,并用hashmap存储函数,便于通过函数名寻找函数。之后再将Scanner.in交给Lexer,进行处理。

函数调用

Parser类进行处理的时候,识别到函数调用后,将函数名与函数实参识别出来后,传入Definer.calcFun,使用Fun.toExpr对表达式进行字符串替换。

需要注意的是,由于是直接采用的字符串替换,因此若是先将形参y替换成带x的实参,再将形参x替换为别的实参时,前一步的实参也会被替换,因此在函数存储时进行形参的换元,即将xyz换为不曾出现的uvw,避免出现问题(替换时也要注意exp中的x)。因为调用后的字符串其实是一串表达式,为了不产生其他影响,在最外面加上一层括号。

替换后,再对新字符串进行解析,返回表达式。

    //Definer.java    
    public static Expr calcFun(String name, ArrayList<Factor> paras) {
        Fun fun = funMap.get(name);
        Lexer lexer = new Lexer(fun.toExpr(paras));
        Parser parser = new Parser(lexer);
        return parser.parseExpr();
    }
​
    //Fun.java
    public String toExpr(ArrayList<Factor> realParas) { // (g(x,3*x), x^2, exp(x))
        String output = content;
        for (int i = 0; i < paraList.size(); i++) {
            String str = "(" + realParas.get(i).toPoly().toString() + ")";
            output = output.replaceAll(paraList.get(i), str);
        }
        output = "(" + output + ")";
        return output;
    }

指数因子

对指数因子的识别只需要在Parser里面加入一个新的方法即可。真正要注意的,是基本项Mono的形式改为了a*x^b*exp(Poly),因此带来的计算逻辑需要有所修改。

最主要的就是实现指数相等的判断。如果Poly中使用hashmap存的基本项,那么可以重写hashcodeequals,直接实现hashmap的相等比较,但是由于我之前使用的是arraylist,因此采取了另一种方式,重写了基本项的equals,然后就可以使用contains函数,从而实现arraylist的比较。

需要注意的是,由于基本项中exp内的指数也是Poly所以判断相等时是两个函数的递归调用。

而在实现了相等后,这一部分其实也变的简单了,仅需要将之前判断系数是否可加减的增加一项调用exp指数的equals便可以满足要求。

优化

本次作业由于实现exp指数相等这里耗费了大量的时间,因此优化只做了一小部分。在toString时对exp里面的多项式输出进行判断,如果是exp(0),则替换为1

想到了提取公因式的方法进行化简,但是没有想好具体的实现措施,而且一些情况下,提取后的性能并不一定比提取前好,遂作罢。

对于exp里面的输出,如果是表达式,则要多加层括号,这个只是通过正则表达式进行识别看里面是否含有"-+*",这点使得我的性能变的较差。在第三次作业中进行了修改。

bug分析

课下
  • null & new

如果用a*x^b*exp(Poly)来存一个基本项,那么对于一个常数来说,Poly的位置存什么,刚一开始,我存的是null,但是这样一来,在所有访问Poly的地方都要进行判断,判断是否为空,但即使这样,也还是不可避免的产生了访问空指针的问题。后来采取了一种,较为浪费空间的方法,即将Poly处存放一个新的对象,但是里面什么也不存,这样便可避免这一问题,就是空间不太友好。

  • MemoryOverFlow

由于在计算时尝试全部拆开再合并,导致中间过程中MLE

强测
  • TLE

本次作业的强测wa了一个点,是TLE了。在debug的过程中,发现了一个令人哭笑不得的逻辑错误。

正常思维下,判断两个基本项是否可加,仅需要判断指数是否相等即可,但是,如果前面系数非零呢?那就不用判断指数是否相等了,而我却没有实现这一逻辑,导致在运算的过程中,使用了保留了过多的零项,对于这些的计算占用大量时间,从而时间爆掉。

互测

在本次互测过程中,发现了一些同学的bug

  • exp(-x)-x是一个表达式,但是他只算一个基本项,因此有两位同学可能只判断了基本项的个数来决定是否加括号,从而导致错误。

  • exp(OverInt):在这次的作业中,由于加入了exp与括号嵌套,从而可以使得指数超过int的范围。

  • 优化的问题:有位同学有如下错误样例,通过合理猜测,这位同学应该是在进行提取公因式时使用了BigInteger除法,而未考虑内部指数为0的情况,并且也未考虑全指数为负的情况。

exp(-1)*exp(1) //BigInteger除零错误
(exp(1)*exp(-2))^3  => exp(1)^-3

方法度量

Method Metrics
Complexity metrics周四21 3月 2024 19:57:32 CST
MethodCogCev(G)iv(G)v(G)
Definer.addFun(String)2222
Definer.calcFun(String, ArrayList<Factor>)0111
Definer.init(Scanner)1122
Definer.transform(String)0111
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()4245
Lexer.peek()0111
Lexer.pre()2123
MainClass.main(String[])1122
Parser.Parser(Lexer)0111
Parser.indexGet()1122
Parser.parseEuler()0111
Parser.parseExpr()5134
Parser.parseFactor()8566
Parser.parseFun()1122
Parser.parseNum()4134
Parser.parseTerm(int)1122
expr.Euler.Euler(Factor)0111
expr.Euler.setIndex(int)0111
expr.Euler.toPoly()0111
expr.Expr.Expr()0111
expr.Expr.addTerm(Term)0111
expr.Expr.setIndex(int)0111
expr.Expr.toPoly()2133
expr.Fun.Fun(String, List<String>)0111
expr.Fun.toExpr(ArrayList<Factor>)1122
expr.Fun.toPoly()0111
expr.Mono.Mono(BigInteger, BigInteger, Poly)0111
expr.Mono.addMono(Mono)1122
expr.Mono.addable(Mono)1212
expr.Mono.clone()0111
expr.Mono.compareTo(Mono)0111
expr.Mono.equals(Object)3414
expr.Mono.isFactor()4244
expr.Mono.multiMono(Mono)1122
expr.Mono.simplify()1112
expr.Mono.toString()2321112
expr.Num.Num(BigInteger)0111
expr.Num.setIndex(int)0111
expr.Num.toPoly()1122
expr.Poly.Poly()0111
expr.Poly.addPoly(Poly)2133
expr.Poly.clone()1122
expr.Poly.equZero()0111
expr.Poly.equals(Object)5525
expr.Poly.isFactor()2323
expr.Poly.monoAdd(Mono)3333
expr.Poly.multiPoly(Poly)5345
expr.Poly.sort()0111
expr.Poly.toString()4335
expr.Term.Term(int)0111
expr.Term.addFactor(Factor)0111
expr.Term.toPoly()2133
expr.Var.Var(int)0111
expr.Var.toPoly()0111
具体分析
  • parseFactor()lexer.next(),进行解耦修改,较上次比复杂度有所降低。

  • 由于新增加了exp指数,导致toString复杂度大增,但是并没有找到可以解决的办法。

第三次作业

本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用、求导算子的表达式,输出恒等变形展开所有括号后的表达式。

总体思路与架构设计

本次作业新增了函数相互调用,求导这两个新功能。

  • 由于上次实现的自定义函数解析本身支持互相调用,因此些许修改(static立大功)

  • 新增Derivation类来保存要求导的内容,toPoly调用Mono的求导并返回Poly

具体细节

求导

Derivation类中的toPoly十分简单,先将内部存储的因子变为Poly,再进行求导。

public Poly toPoly() {
    Poly poly = factor.toPoly();
    return poly.derivation();
}

对于Poly的求导其实就是将内部所有Mono的求导之和相加。

针对于基本项a*x^b*exp(Poly),可分为四类:常数,没有x,没有exp,x与exp都有,而最后两种情况会有多项结果,因此求导返回Poly对象。

自定义函数

对于此部分的修改其实也较为简单,只需要对右边的表达式的正则表达式进行修改即可,使其支持fgh函数名的识别以及参数间隔的,

String regex = "([fgh])\\(([,uvw]*)\\)=([\\w()+\\-*^,]+)"

优化

本次的优化主要针对了exp里面什么时候该加括号,什么时候不加。

首先明确,加括号是在Poly中的每一项的mono进行判断,而判断mono是否加括号,只需要判断exp里面是否只是一个因子,将这个方法记为poly.isFactor(),而判断poly.isFactor()需要进行访问里面的具体内容,因此还得写一个mono.isFactor()

    //Poly.java
	private boolean isFactor() {
        if (monos.size() > 1) {
            return false;
        }
        for (Mono mono : monos) {
            return mono.isFactor();
        }
        return true;
    }

	//Mono.java
    public boolean isFactor() {
        if (ceo.compareTo(BigInteger.ZERO) != 0 && ceo.compareTo(BigInteger.ONE) != 0) {
            return index.compareTo(BigInteger.ZERO) == 0 && exp.equals(new Poly());
        } else {
            return index.compareTo(BigInteger.ZERO) == 0 || exp.equals(new Poly());
        }
    }

因此只需根据isFactor的返回值,决定是否加括号就行了。

对于提公因式的方法也进行了尝试,但是出现了负优化与bug,随放弃。

方法度量

Method Metrics
Complexity metrics周五22 3月 2024 18:00:12 CST
MethodCogCev(G)iv(G)v(G)
Definer.addFun(String)2222
Definer.calcFun(String, ArrayList<Factor>)0111
Definer.toBeDecided(Scanner)1122
Definer.transform(String)0111
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()4245
Lexer.peek()0111
Lexer.pre()2123
MainClass.main(String[])1122
Parser.Parser(Lexer)0111
Parser.indexGet()1122
Parser.parseDerivative()0111
Parser.parseEuler()0111
Parser.parseExpr()5134
Parser.parseFactor()9677
Parser.parseFun()1122
Parser.parseNum()4134
Parser.parseTerm(int)1122
expr.Derivative.Derivative(Factor)0111
expr.Derivative.toPoly()0111
expr.Euler.Euler(Factor)0111
expr.Euler.setIndex(int)0111
expr.Euler.toPoly()0111
expr.Expr.Expr()0111
expr.Expr.addTerm(Term)0111
expr.Expr.setIndex(int)0111
expr.Expr.toPoly()2133
expr.Fun.Fun(String, List<String>)0111
expr.Fun.toExpr(ArrayList<Factor>)1122
expr.Fun.toPoly()0111
expr.Mono.Mono(BigInteger, BigInteger, Poly)0111
expr.Mono.addMono(Mono)0111
expr.Mono.addable(Mono)1212
expr.Mono.clone()0111
expr.Mono.compareTo(Mono)0111
expr.Mono.derivation()5155
expr.Mono.equals(Object)3414
expr.Mono.isFactor()5255
expr.Mono.multiMono(Mono)1122
expr.Mono.simplify()1112
expr.Mono.toString()2321112
expr.Num.Num(BigInteger)0111
expr.Num.setIndex(int)0111
expr.Num.toPoly()1122
expr.Poly.Poly()0111
expr.Poly.addPoly(Poly)2133
expr.Poly.clone()1122
expr.Poly.derivation()1122
expr.Poly.equZero()0111
expr.Poly.equals(Object)5525
expr.Poly.isFactor()2323
expr.Poly.monoAdd(Mono)3333
expr.Poly.multiPoly(Poly)5345
expr.Poly.sort()0111
expr.Poly.toString()4335
expr.Term.Term(int)0111
expr.Term.addFactor(Factor)0111
expr.Term.toPoly()2133
expr.Var.Var(int)0111
expr.Var.toPoly()0111
分析

还是toString爆了!!!!,设想将toString分开成为几个函数,分别生成对应代码,但是感觉拆开后这几个的方法仍然有相当高的耦合度,也不符合oo的要求。

同时parseFactor,也有些大,因为其调用了多个不同因子的处理函数,导致条件判断过多。

Bug分析与思考

最后一次的bug,主要产生在优化方面,因为别的内容基本没有修改,而求导方法的实现所需要考虑的情况数也不多。

首先是我的优化bug,在进行加括号判断的时候,最外层的多项式也会被加上括号,但是由于这是基于我的架构的最简单判断是否加括号的方法,因此对于此bug的修改只是将最外层的括号删去。

优化中其实有一部分是基础性优化,是很轻松就可以拿到的分数,比如合并同类项,比如正号提前,将这些做到,便可以拿到80~90%的性能,因为正确性还是首要的,因此可以进行化简而得到很短的表达式的数据点很少。而剩下一两个则是专门为优化设置的数据点,如果你没有想到最简的那种优化,大概率还是性能得不到分。

而在评测中,性能占用15%,而正确性占85%,一个测试点的正确分就抵得上近6个性能分。进行一个优化,而导致测试出现bug,是一件很亏的事情,况且优化在某些情况下可能还是负优化,所以除非是特别笃定这个优化一定没有错误,并且绝大多数情况下都是正优化,那么可以提交,反之还是要慎重。

当然,这样并不是说不去优化,而是说在优化后要多进行测试,保证不会出现问题,即使这一部分要用比优化更长的时间。

总结

迭代式开发

单次作业

在每一次单独作业进行开发的时候,也要注意迭代的思想。比如在第二次的作业中,在构建前,先想清楚要分为哪几步,比如自定义函数表达式的存储,自定义函数的调用,exp指数基本项的实现,exp的计算修改。然后再迭代进行,每次进行完一步之后,对上一步的功能进行检查与验证,无误后在进行下一步。在这一过程中,对于迭代目标也有可能进行一定的修改,但是这本身也是一种正常的迭代。相比较第一次从头到尾的开发,对于代码的整体框架的有了更清楚的把握。

作业之间

前一次在设计的时候,尽量考虑周全,考虑可拓展性,尤其要注意一些边界条件的设计要求,比如说,第一次要求不会出现嵌套括号,便要考虑后续是否会需要支持多层括号。又比如题目要求只有未知变量x,会不会出现yz等其他变量。这些都应该在设计时都考虑进来,如果加了这些要求,要怎样才能在原来的基础上进行少量的增添就能解决这一问题,或者直接一次设计就支持后面的部分功能。

类的架构

本次作业中的类的架构其实是有一些是问题的,一部分的类有些冗余,另一部分类有些过度耦合。

Mono与Term

mono类实现了基本项的存储,但是我却把他与term分开,但是mono都叫做基本了,那其实他和term类的逻辑关系是很密切的。但是他们两个的内容还是不太一样的,采用继承的方式也不是很能描述这一关系。或许也应该使用接口,将两个类都继承一个新的NewTerm的接口,从而最终可以不去新建Poly类,而是使用Expr类解决这一问题。

Mono与Poly

在我的实现中,Poly内部会有arraylist来存mono,而mono里面的exp又会去保存一个Poly,层层嵌套,导致函数的计算写起来也得层层调用,debug时候更是难上加难。但是仔细想来,由于exp里面深度的不确定性,也只能写这样的一个循环。并不能将这两个方法进行解耦或者是合并。

exp没留指数

Poly的设计中,并没有保留指数这一属性,导致优化时提因子后,并不能有一个合理的位置来存储,从而导致公因数来回传参,最终使得这一架构在优化过程中变的十分棘手,没有找到很好的既可以进行优化,又能保证正确性的方法。

寻找bug

首先通过自动数据生成器构造大量数据,进行测试,若有问题,则将该数据点进行一点点的拆解,将不会造成bug的部分删去,最终得到诱导bug的数据。

而若是数据生成器测不出问题,则通过自己在写代码时出现的问题,进行进一步的测试,同时构造一些特殊情况,或者进行压力测试。

心得体会

本单元的作业总体经历了三个阶段,迷茫无助,痛苦煎熬,如释重负。在好久没有接触oo并且上来就是完全没有学过的递归下降,与复杂的形式化定义,对于如此庞然大物的处理,有些不知所措,所幸有老师、有助教、有往届学长留下的博客,得以让我成功度过第一周。而第二周的exp是全新的内容,思考其相等的判断,以及PolyMono的反复调用,令我cpu过载,每天从早到晚,充斥着oo,甚至梦里也没有放过我,不过还好,挺过来了。感谢前两周的辛勤努力与对可拓展性的思考与实现,使得我在第三次的作业中,较为轻松。

至于未来的几个单元,且行且看,无论有多么痛苦与无助,相信我一定能坚持下来,再次达到如释重负。

未来方向

可以考虑展出一些优秀的架构的与参考,仅凭大家课堂展示的分享,可能并不能get到其中架构设计时的精华所在。要在课下将自己的架构与优化架构进行反复的比较与思考,才能向更好的架构前进。

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值