buaa oo 第一单元

本文介绍了使用面向对象编程思想,通过递归下降方法解析含x,y,z变量的表达式,涉及括号处理、恒等变形、自定义函数和三角函数的处理。在第二次作业中增加了三角函数因子和自定义函数,而第三次作业实现了求导运算,通过DeriveFactor类处理每一层结构的求导。
摘要由CSDN通过智能技术生成

OO 第一单元

综述:

  本单元要求对一个含有x,y,z变量的表达式进行解析,从而进行拆括号、求导等任务,目的是为了熟悉面向对象编程的思想,在设计时我主要采用了递归下降的分析方法,现将我近三周的学习作以下总结和反思。

第一次作业:

题目要求:

  对一个含有加、减、乘、乘方以及括号的多变量表达式进行恒等变形,使得结果没有括号。

表达式结构可分解为如下形式:
在这里插入图片描述

  为了解析表达式,我创建了一个Expression类,一个Term类,一个Factor接口,Factor具体由NumFactor、ExpFactor、PowerFactor来实现。

代码架构

在这里插入图片描述

预处理

  根据老师上课的讲解,main函数的应该分为输入,问题处理、输出三个部分,因此我建立了一个Preprocess类,里面有一个standardize的方法对输入的字符串进行处理。

//先消掉空白符
          String after = before.replaceAll("\\s*", "");
//对连续的加减进行处理
        StringBuilder str = new StringBuilder(after);
          int i = 0;
          while (i < str.length() - 1) {
            if (str.charAt(i) == '+' && str.charAt(i + 1) == '+') {
                str.replace(i, i + 2, "+");
            } else if (str.charAt(i) == '-' && str.charAt(i + 1) == '+') {
                str.replace(i, i + 2, "-");
            } else if (str.charAt(i) == '+' && str.charAt(i + 1) == '-') {
                str.replace(i, i + 2, "-");
            } else if (str.charAt(i) == '-' && str.charAt(i + 1) == '-') {
                str.replace(i, i + 2, "+");
            } else {
                i++;
            }
        }
字符串解析

  在解析字符串的时候我采用了递归下降的方法,递归下降法指的是给定文法之后,一层一层地解析语句的成分。比如在本次任务中我们要解析的语句是表达式,表达式可以看作是项的和或差,项可以看作因子的乘积,因此在解析的时候可以先解析表达式,在解析表达式函数内部实现对项的解析,而解析项的过程其实是对因子的解析进而将其组合成项。

  在这一部分主要有两个类,一个Parser类,一个Lexer类。

我们先来看Lexer:

public class Lexer {
    private String str;//解析的字符串
    private int pos;//当前解析的位置
    private String current;//当前读到的元素
    private Type type;//当前读到的类型
       public void read() {
        current = "";
        pos++;
        if (pos < str.length()) {
            char c = str.charAt(pos);
            if ("dxyz+-()fgh,".indexOf(c) != -1) {
                current = String.valueOf(c);
                type = findType(c);
            } 
    }
    }
    //read方法:移动pos,读入当前的元素,对current和type赋值,元素包括:数字、变量、左括号、右括号、乘号等。
    ……

Parser类与lexer类协作,根据lexer读到的元素对字符串进行解析,最终会得到一个表达式。

public class Parser {
    private final Lexer lexer;
    private final Expression expression;

        public Expression parseExpression() {
        Expression expression = new Expression();
        expression.addTerm(parseTerm(), false);
        //Expression是Term的和
        ……
        return expression;
    }

    public ArrayList<Factor> parseFactor() {
        ArrayList<Factor> factors = new ArrayList<>();
        lexer.read();
        if (lexer.getType() == Type.alpha) {
            String str = lexer.getCurrent();
            factors.add(new PowerFactor(str, getExp()));}
            //通过判断Type,找到对应的Factor
            ……
            }
}
需要注意的点:

1.因子前面可能有符号,对应的处理方法是遇到符号,就加入一个对应的NumFactor

            if (lexer.getType() == Type.minus) {
                factors.add(new NumFactor(new BigInteger("-1")));
            } 

2.乘方可能存在,可能不存在,且存在的时候也可能有‘+’号,因此我定义了一个getExp()方法,用来获取可能存在的指数。

            lexer.read();
            lexer.read();
            if (lexer.getType() == Type.add) {
                lexer.read();
            }
            exp = Integer.parseInt(lexer.getCurrent());
            return exp;

在new一个Factor的时候嵌入getExp方法即可.

new PowerFactor(str, getExp())

3.在解析表达式时,我将表达式全部看作项的和,因此如果遇到一个负号时,我会给后面的Term乘以-1,方便进行后续的运算。

恒等变形

  现在我们已经完成了整个表达式的解析工作,接下来要自下而上地将每个元素转化为多项式,参考往届学长思路,我将多项式Poly看作是单项式Mono的和,Mono具有两个属性,一个是BigInteger类型,用来表示系数,一个是大小为3的数组,分别对应x,y,z的指数。

对每一个Factor,都实现了一个toPoly方法,以PowerFactor为例

    public Poly toPoly() {
        int[] expSet = new int[3];
        expSet[var.charAt(0) - 'x'] = exp;
        Mono mono = new Mono(BigInteger.ONE, expSet, new HashMap<>(), new HashMap<>());
        Poly poly = new Poly();
        poly.addMono(mono);
        return poly;
    }

  对于Term,将其转为多项式的方法是将Term中的每个Factor转化成Poly,然后将Poly相乘。
  而对于Expression,则是将Expression中的每个Term转化成Poly,然后相加。
  这里我们需要实现multiPoly和addPoly方法,add只需要将每个mono加入已有的数组中,而multiply则需要取出每个Poly中的Mono依次相乘。

        Poly poly = new Poly();
        for (Mono mono1 : poly1.getMonoList()) {
            for (Mono mono2 : poly2.getMonoList()) {
                Mono multiMono = multiMono(mono1, mono2);
                poly.addMono(multiMono);
            }
        }
输出

最后实现Poly和Mono的toString即可,Mono的toString较为细节,需考虑多种情况。
1.系数、指数全部为0
2.指数为1的时候不输出指数

可能出现的bug

1.整数有前导0,且带有正负号。
2.指数符号后面的数字可能带有正号。

总结

  第一次作业难点在于理解递归下降方法,并对面向对象的抽象思想有整体把握,同时预处理的思想很重要,处理好连续正负号问题为后续的解析提供了很大便利,因为是第一次所以代码量较大,但只要想清楚框架便不难实现。

第二次作业

任务要求

本次作业相比于第一次增加了以下几点:
1.支持嵌套括号(因为第一次已经实现所以本次无需考虑)
2.新增三角函数因子,即sin(<因子>)和cos(<因子>),且可能含有指数
3.支持最多三个自定义函数,形如f(x,y,z)=表达式,调用时则是f(因子,因子,因子)的形式,须注意的是,参数不一定是三个且不保证顺序,调用时因子也可以是函数因子。

代码架构

在这里插入图片描述

三角函数处理

  针对三角函数我采用了一个RectangleFactor作为父类,其有两个属性,一个Factor表示括号内的因子,一个exp表示指数,用SinFactor和CosFactor来继承。

自定义函数处理

  首先我们要解析自定义函数,这里我在Preprocess类中增添了parseFunc方法,通过正则表达式来获取函数名,参数表和对应的表达式字符串。

        Pattern pattern = Pattern.compile("([fgh])[(](.+)[)](=)(.+)");
        Matcher matcher = pattern.matcher(temp);
        if (matcher.find(0)) {
            char[] para = new char[3];
            int sum = 0;
            String paraList = matcher.group(2);
            for (int i = 0; i < paraList.length(); i++) {
                if (paraList.charAt(i) >= 'x' && paraList.charAt(i) <= 'z') {
                    para[sum++] = paraList.charAt(i);
                }
            }
            Functions.setFunction(matcher.group(1), para, matcher.group(4));
        }

  其次我定义了一个Functions类,其中的属性和方法都是静态的,主要用来存放读取到的函数,并进行实参的代换。

public class Functions {
    private static HashMap<String, String> functions=new HashMap<>();
    //key是函数名,value是对应的表达式字符串
    private static HashMap<String, char[]> parameters= new HashMap<>();
    //key是函数名,value是对应的参数表

     //该方法用来存放读入的函数。
        public static void setFunction(String name, char[] para, String expr) {
                          functions.put(name, expression);
        parameters.put(name, para);
    }

//该方法的作用是给定函数名以及对应的实参,返回实参带入函数表达式后的字符串
  public static String exchange(String func, ArrayList<String> factors) {
        StringBuilder ans = new StringBuilder(functions.get(func));
        char[] paraList = parameters.get(func);
        for (int i = 0; i < 3; i++) {
                  //这里我将原来的x,y,z变量替换成了p,q,r防止替换出错
            if (paraList[i] >= 'p' && (paraList[i]) <= 'r') {
                int j = 0;
                while (j < ans.length()) {
                    if (ans.charAt(j) == paraList[i]) {
                        ans.replace(j,j+1, "(" + factors.get(i) + ")");
                        //一定要记得加括号
                    } else {
                        j++;
                    }
                }
            }
        }
        return ans.insert(0, "(").append(")").toString();
    }

  对于函数因子我建立了FuncFactor变量,它含有一个Expression属性,在构造函数中会通过函数名和参数表将这个Factor转化成对应的Expression。

        Lexer lexer = new Lexer(Functions.exchange(func, factors));
        Parser parser = new Parser(lexer);
        this.expression = parser.getExpression();
恒等变形

  与第二次作业相比,本次作业的单项式中需要增加sin函数和cos函数作为因式,且数量不定,所以我用两个HashMap来分别表示单项式中的sin因子和cos因子。

    private HashMap<Factor, Integer> sinMap;
    private HashMap<Factor, Integer> cosMap;
    //key为括号内的因子,value为对应指数

  然后对之前的addPoly方法和multiPoly方法以及最后的toString方法进行修改即可。

优化

  这次作业中单项式增加了sin函数和cos函数作为因式,且数量不定,括号内的银子也比较多样,这就给合并同类项带来了巨大困难,笔者认为本次作业的难点也在于此。

下面只对两种优化进行举例说明:
1.除系数外其他项均相等,无需增加新的Mono,系数相加即可。

            if (Mono.equalMono(mono, origin)) 
                origin.addCoe(mono.getCoe());

    public static boolean equalMono(Mono mono1, Mono mono2) {
        return (mono1.exp[0] == mono2.exp[0]) && (mono1.exp[1] == mono2.exp[1]) &&
            (mono1.exp[2] == mono2.exp[2]) && equalMap(mono1.sinMap, mono2.sinMap) &&
            equalMap(mono1.cosMap, mono2.cosMap);
    }                

2.( s i n x 2 sinx^{2} sinx2)+( c o s x 2 cosx^{2} cosx2)=1

此处我实现了一个foldable方法,用来判断两个Mono是否可以合并。

    public static Mono foldable(Mono mono1, Mono mono2) {
        if (mono1.coe.equals(mono2.coe)) {
            if (mono1.exp[0] == mono2.exp[0] && mono1.exp[1] == mono2.exp[1] &&
                mono1.exp[2] == mono2.exp[2]) {
                for (Factor factor1 : mono1.sinMap.keySet()) {
                    if (mono2.cosMap.containsKey(factor1) && mono1.sinMap.get(factor1) == 2 &&
                        mono2.cosMap.get(factor1) == 2) {
                        HashMap<Factor, Integer> clone1 = cloneMap(mono1.sinMap, factor1);
                        HashMap<Factor, Integer> clone2 = cloneMap(mono2.cosMap, factor1);
                        if (equalMap(clone1, mono2.sinMap) && equalMap(clone2, mono1.cosMap)) {
                            return new Mono(mono1.coe, mono1.exp, clone1, clone2);
                        }
                    }
                }
……
可能出现的bug

1.因为我的sinMap和cosMap是用自定义类作为Factor的,因此需要对Factor重写hashcode和equals方法,并且要保证Factor在put之后,其属性不变,否则就会导致同一个Factor前后的hashcode值不同。

2.输出时三角函数括号内如果是表达式或者函数,需要加括号。

3.判断两个Mono相等可以不比较coe

总结:

第二次作业主要耗时在了优化上,bug也几乎出于此。过程中我了解了java的hashmap实现机理,也掌握了hashcode和equals的重写方法。

第三次作业

任务要求

  第三次作业增加了两个要求:一是支持求导运算,二是可以进行函数的嵌套调用。

代码架构
函数嵌套

  为了减少代码修改量,我在预处理部分就解决了函数嵌套调用及定义式中有求导符号的问题,具体如下:
1.如果函数表达式中调用了之前的函数,则用被调用函数的表达式替换调用者中的对应项。

        if (expression.indexOf('g') != -1 || expression.indexOf('h') != -1 ||
            expression.indexOf('f') != -1) {
            expression = insert(expression);
        }
        public static String insert(String expression) {
        StringBuilder ans = new StringBuilder(expression);
        for (String origin : functions.keySet()) {
            while (ans.indexOf(origin) != -1) {
                int start = ans.indexOf(origin);
                ArrayList<String> factors = new ArrayList<>();
                int end = cut(ans.substring(start + 2), factors) + start + 2;
                ans.replace(start, end + 1, exchange(origin, factors));
            }
        }
        return ans.toString();
    }

2.如果表达式中含有求导符号,则将其看作表达式进行解析,返回最后toString的结果。

        if (expression.indexOf('d') != -1) {
            expression = derive(expression);
        }
求导运算

新建一个DeriveFactor类,然后为每一层的结构写一个Derive方法即可。

    public Expression derive(char x) {
        Expression derivedExp = new Expression();
        for (Term term : terms) {
            Expression derivedTerm = term.derive(x);
            for (Term derived : derivedTerm.terms) {
                derivedExp.addTerm(derived, false);
            }
        }
        return derivedExp;
    }
总结

  第三次作业并不难,把求导看成一种运算,每个结构递归调用运算方法,最后仍能得到表达式,用之前的方法处理表达式即可。

可能出现的bug

  第三次作业较为顺利,没有遇到什么问题。

代码复杂度分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值