BUAA_OO_第一单元总结

本文详细描述了BUAA_OO_第一单元编程作业,包括代码结构分析(如类的复杂度和功能)、递归下降算法的应用、后缀表达式处理、架构设计中的难点与优化,以及Bug的发现和处理。作业强调了面向对象原则和代码可扩展性的实践。
摘要由CSDN通过智能技术生成

BUAA_OO_第一单元总结

1 程序结构分析

1.1 代码规模

  • 下面是各个类的代码长度
    在这里插入图片描述
  • 代码总行数1604行,源代码1414行,占比88%。可以看出,从头到尾采用后缀表达式去计算的话,代码还是很复杂的,代码长度也较多,但是整体上思路还是比较容易理解的,对于细节的要求比较复杂。
  • Operate行数最多,里面主要是针对后缀表达式的处理,包括乘法,加法等等,其中乘法和加法的步骤较为复杂。
  • Mainclass行数也较多,里面主要是进行对函数的预处理,以及自定义函数的替换。

1.2 类和方法的相关度量

利用IDEA的Calculate Metrics中的Class Metrics对类的复杂度进行分析

  • OCavg(Average opearation complexity):平均操作复杂度
  • OCmax(Maximum operation complexity):最大操作复杂度
  • WMC(Weighted method complexity):加权方法复杂度
    在这里插入图片描述
    下面选取两个复杂度较高的类的方法进行复杂度分析:
    Mainclass里面方法的复杂度分析
    可以发现,主函数的复杂度还是比较高的。尤其是预处理阶段,需要对字符进行逐一的判断。
    还有就是针对自定义函数的处理的复杂度也比较高。
    在这里插入图片描述
    Operate类里面方法的复杂度分析
  • Add方法涉及对两个算式进行相加,需要对两个字符串进行分割,然后计算,复杂度较高。
  • depart(String),departpart(String),departspace(String)功能类似,分别是对一个字符串安照+,*, 进行分割,方便进行下一步的操作,复杂度较高。
    在这里插入图片描述

1.3 类图

  • 由于整体代码不断在迭代,最后一次代码也能实现第一次的功能,所以只贴出最后一次代码的类图以及之间的关系。

1.4 总结

  1. 这次作业的类复杂度很高,部分类之间耦合程度高。但是继承较少,多数类的功能互不影响;
  2. Mainclass类中主要是针对函数表达式的预处理以及自定义函数的处理,涉及到一些字符串的替换以及循环,所以操作复杂度较高;
  3. 对于Operate类的函数,主要是用于加法和乘法的一些运算以及性能优化部分,所以不仅连续嵌套循环,还要不断地调用其他类中的函数。这就导致在计算和化简时,类之间反复调用,圈复杂度很高,也导致了耦合度大;
  4. Term,Expr,Number,Var,Ex, Derivation等类与其他类间几乎没有干扰,作为因子完整地实现自身功能,可以说实现了一部分的“高内聚”原则。
  5. 关于每个类的具体功能,下面会在架构设计体验中仔细介绍。

2 架构设计体验

2.1 Homework1

本次作业的难点主要是对于递归下降算法的理解,第一次作业是起步阶段,是相应的来说最难去突破的一次作业。要从零开始写,一开始我是对递归下降没有任何认知的,需要从零开始去理解,还好有课上的实验课帮助理解,才能够按时的完成。本程序,包括后面的几次作业,都是使用递归下降法将表达式的后缀表达式算出来,然后再进行相应的计算,最后化简并输出。

2.1.1 输入预处理

输入的表达式可以通过一些预处理来简化后面的解析过程。

  • 对于空格,全部通过replaceAll方法换掉。
  • 将连续的+和-替换为-
  • 将连续的+和+简化为一个+
String result000 = result0000.replace(" ", "");
String result00 = result000.replace("\t", "");
		if (result.charAt(i) == '+') {
                if (result.charAt(i + 1) == '+') {
                    result = result.substring(0,i + 1) + result.substring(i + 2);
                    i--;
                }
                else if (result.charAt(i + 1) == '-') {
                    result = result.substring(0,i) + result.substring(i + 1);
                    i--;
                }
            }
            else if (result.charAt(i) == '-') {
                if (result.charAt(i + 1) == '+') {
                    result = result.substring(0,i + 1) + result.substring(i + 2);
                    i--;
                }
                else if (result.charAt(i + 1) == '-') {
                    result = result.substring(0,i + 1) + result.substring(i + 2);

                    StringBuilder builder = new StringBuilder(result);
                    builder.setCharAt(i, '+');
                    result = builder.toString();

                    i--;
                }
            }
            else if (result.charAt(i) == '*') {
                if (result.charAt(i + 1) == '+') {
                    result = result.substring(0,i + 1) + result.substring(i + 2);
                    i--;
                }
            }
2.1.2 解析表达式得到后缀表达式

解析表达式主要利用Lexer和Parser两个类,通过递归下降的算法,得到后缀表达式进行后面的计算。

Lexer lexer = new Lexer(result00);
Parser parser = new Parser(lexer);
Expr expr = parser.parseExpr();
  • Lexer类通过lexer.next()来读取下一个数字或者参数,通过lexer.peek()来得到当前读取到的值。
  • Parser负责解析表达式,并且生成表达式树。主要有parserExpr()和parserTerm()和parserFactor()三种方法。
2.1.3 通过栈的方法来计算后缀表达式的值
		for (int i = 0;i < parts.length;i++) {
            if (parts[i].equals("+")) {
                String str = Add(stacks[top], stacks[top - 1]);
                top--;
                stacks[top] = str;
            }
            else if (parts[i].equals("*")) {
                String str = Mul(stacks[top], stacks[top - 1]);
                top--;
                stacks[top] = str;
            }
            else {
                top++;
                stacks[top] = parts[i];
            }
        }
2.1.4 进行最后的化简
  • 将空格删去
  • 将连续的加减号化简掉
2.1.5 第一次作业总结

由于本次的代码未能留存,且后续的几次代码是在此代码上进行迭代而来的,所以在此不贴上类图。本次作业在强测和互测中并未出现bug,但是有很少的性能分丢失,但是几乎可以忽略不记。

2.2 Homework2

老师在课上强调本次作业将会是整个oo作业中最难的一次作业,事实也确实是这样,至少是第一单元最难的一部分。这一次的难点主要是对于指数部分的处理,函数替换的部分可以通过字符串替换的方式进行,但是指数部分的处理到周五才刚刚有思路,经历了几天的焦虑以后,终于完成了这一次作业。本次作业仍然是沿用上一次的架构,使用计算后缀表达式的方法,最后对后缀表达式进行计算。本次作业主要分为两个部分,自定义函数和指数部分的处理,同时引入了一个一般形式Normal类,下面将依次介绍。

2.2.1 自定义函数的解决

在下图中:

  • s0代表着要计算的式子
  • xingcan里面存储的是类似于<‘f’,“xyz”>这种形式,biaodashi里面存储的函数定义式中的=后面的部分
  • 需要注意的是,再读入函数定义式时,需要将空格全部消除,以及避免可能出现的形参混乱的情况,将表达式里面的xyz全部替换为pqr
public static String removeFxson(String s0, HashMap<Character, String> xingcan,
                                     HashMap<Character, String> biaodashi) {
        String s = s0;
        for (int i = 0;i < s.length();i++) {
            if (s.charAt(i) == 'f') {
                String shican = zhao(i, s);
                String tihuanqian = "";
                tihuanqian = 'f' + shican;
                shican = shican.substring(1, shican.length() - 1);
                String[] shicans = devide(shican);
                String xingcans = xingcan.get('f');
                String tihuanhou = biaodashi.get('f');
                for (int k = 0;shicans[k] != null;k++) {
                    String bushican = '(' + shicans[k] + ')';
                    String buxingcan = String.valueOf(xingcans.charAt(k));
                    tihuanhou = tihuanhou.replace(buxingcan, bushican);
                }
                tihuanhou = '(' + tihuanhou + ')';
                s = s.replace(tihuanqian, tihuanhou);
                i = -1;
            }
            //后续是针对g和h函数的处理,在此省略
      }
}
2.2.2 指数部分的处理

针对于指数部分,在借鉴了学长代码的思路下,终于有了解决办法:

  • 避免代码中对于x的处理出现麻烦,此处首先将exp全部替换为x,在输出时再将e全部替换为exp
  • 再读到e这个字符时,进入到读因子的模块(此处只是说大致思路,期间还有需要处理的细节,对lever.peek()的控制等等),然后将这个因子作为一个指数因子的一个参数。
public class Ex implements Factor {

    private Factor inex;

    private int cishu = 1;
    //下面有一些方法的实现,在此略过
}
  • 在输出时:需要将Factor的后缀表达式重新调用一遍计算后缀表达式的过程
public String toString() {
        Operate son = new Operate("aaa");
        String out = son.operator(this.inex.toString());
        out = "(" + out + ")";
        if (this.cishu == 1) {
            return "e(" + out + ")";
        }
        else {
            return "e(" + out + ")^" + String.valueOf(this.cishu);
        }
    }
2.2.3 有关Normal类的介绍
public class Normal {

    //a*x^b*hashMap
    private BigInteger aa;
    private BigInteger bb;
    private HashMap<String, BigInteger> hashMap = new HashMap<>();
    //下面省略一些方法
}

一个Normal类的一般形式是:
在这里插入图片描述

  • 在Operate函数里面,计算乘法时,后首先将字符串分割成不含加法的形式,然后将两个字符串化成Normal类,再进行计算。
2.2.4 第二次作业总结

本次作业首先是纠结于到底要不要进行重构,发现不重构也能做,然后是不会处理指数部分,最后事不会建立一般形式,最后才发觉一般形式里面可以装一个HashMap,本次作业强测和互测都被发现了bug,是没有进行充分的本地测试,没有利用好测评机资源的原因,也在第三次作业中使用测评机发现了许多bug

2.3 Homework3

本次作业主要是增加了求导这一个功能,以及函数在定义时可以调用其他定义的函数,但是这一个地方如果上一次作业是采用字符串替换的方式进行,就不会产生错误。下面主要介绍的方法。

2.3.1 求导过程的实现
  • 首先是预处理阶段,为避免可能出现的不必要的麻烦,首先将所有的dx替换为d
  • 读因子时,如果遇到d,就进入读入一个表达式的过程,读入的这个表达式算作这个求导因子的参数
  • 然后每一个因子类都增加一个方法todxString(),用作求导方法,下面是新增加的Derivation类,是求导因子。
public class Derivation implements Factor {
    private final Expr expr;

    public Derivation(Expr expr) {
        this.expr = expr;
    }

    public String toString() {
        return this.expr.todxString();
    }

	public String todxString() {
        Operate grandaughter1 = new Operate("grandaughter1");
        String ooo = grandaughter1.operator(this.toString());
        ooo = ooo.replace(" ", "");
        ooo = "d(" + ooo + ")";

        Operate grandaughter2 = new Operate(ooo);
        String out = grandaughter2.op();

        String outout = chuli(out);
        return outout;
    }
    //chuli()是将结果化成后缀表达式的形式
}
2.3.2 第三次作业总结

本次作业是较为简单的一次,很多同学在题目放出来当晚就已经完成了作业,但是我用后缀表达式去做的话,可能需要更注重细节上的实现,多一个空格可能都会导致结果错误,此次作业也学会了使用评测机进行找bug,还是能找出来很多bug的。

2.4 自定义新迭代情景说明可扩展性

  • 比如在下一次迭代过程中,增加了三角函数的因子类。在这种情况下,其实和指数函数因子类的处理方法大致相同。新增加一个Sin类和Cos类,在其中实现求导和输出的方法。作业完成过程中没有经历过大的变动,也说明程序的可扩展性良好。

3 Bug分析

  • 在第一次作业的互测和强测中没有发现bug。
  • 在第二次作业中,发现了几个bug。
    当跑程序0 (((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8时,程序直接报错。这儿我意识到了进行表达式toString方法的实现的时候,需要进行适当的判断。
    在这里插入图片描述
    还有就是类似于exp(-1)这种情况,没有处理好exp()里面是有负号的这种情况。
    在这里插入图片描述
  • 在第三次作业中,好在利用了测评机,没有在强测和互测中发现bug,但是在自己找bug的时候遇到了一些bug,我在预处理的过程中会将()的零次方替换成1,但是如果同时出现(x+1)^0(x+1)^000时,替换时会出现错误。

4 优化策略

  • 还是最经典的那一点,连续的加减号化简成只有一个加减号的形式。
  • 还有就是关于exp()*exp()的形式,由于我的思路中采用的是不进行合并,所以在最后我也并没有针对这一个地方进行优化,但是这儿是会导致严重的性能失分。

5 心得体会

  • 想好思路再动手:OO第一单元主要要求将一个表达式展开、化简,同时要求化简结果正确而且不能带有不必要的括号。这次作业是我第一次接触这么复杂的Java程序,因此在完成作业时较为吃力,而且初次完成设计时有很多不健全的架构设计,在后面的每次迭代中都需要进行一定的改动。这也提醒我,在接下来的OO作业中要充分考虑好后面可能需要的迭代功能,避免重构带来的巨大压力。还有就是充分利用好同学的测评机资源,尽量避免一些小错误的发生。
  • 多关注讨论区:讨论区可能有一些需要注意的点,比如讨论区提到,为了避免一些可能发生的错误,可以在读入函数的阶段,将xyz替换为pqr,避免可能发生的错误
  • 总的来说,本单元一共又三次作业,三次作业层层递进,这对代码的可拓展性有一定要求,而多种因子类型又要求实现多态,这让我体会到面向对象思维的重要性,加深我了对封装,继承与多态等性质的理解,增强了自己的代码能力。

6 未来方向

  • 作为从头到尾都采用后缀表达式来做的方法,可能对面向过程的要求更高,需要频繁处理字符串,以及考虑字符串输出的格式,可以更好的考虑代码架构,代码长度和复杂度也可以相应的优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值