OO第一单元 —— 表达式解析

周子皓-22371187 学生 2024-03-19 10:43:44

OO_homework1 - 表达式解析


第一次作业

UML类图

img

代码架构思路分析

本次作业主要内容可以简单分为三部分:预处理解析表达式计算化简

  1. 预处理
    预处理部分比较简单,删除空格和合并多余的符号即可。合并符号可以通过Java的内置方法实现:nihao
while (input.contains("++") || input.contains("--")
                      || input.contains("+-") || input.contains("-+")) {
                  input = input.replaceAll("\\+\\+", "+");
                  input = input.replaceAll("\\+-", "-");
                  input = input.replaceAll("-\\+", "-");
                  input = input.replaceAll("--", "+");
              }

使用while实现递归查找,保证所有的重复符号都被替换。实际上本人由于性格叛逆,一开始没有使用预处理方法,而是选择在Parser类解析过程中使用控制流来处理符号,这样导致方法冗长并且由于没有注意形式化表达,忽略了+-+1的情况。(表达式,项,因子各带一个符号)

  1. 解析表达式
    解析表达式使用 递归下降 进行解析主要分为两个部分:LexerParser其中Lexer负责解析输入的字符串并返回输出,Parser根据返回的输出进行解析。举个例子:x*(x+3*x^3)+x^2+1(1)整个式子就是一个 表达式 ,因此直接进入ParseExpr方法,ParseExpr中使用+``-进行分割,第一个项直接调用ParseTerm,随后每读到一个符号之后就再调用一次ParseTerm进入对 的分析。在进入 的分析之前,我们很容易注意到, 之前有两种符号而因子只有一个。这就意味着 在组成***的时候复杂度提高了。为了避免这种复杂度,我们将符号作为 的一个属性,这样的话就可以简单把 相加来组成 表达式 了。在本例中直接进入第一项``x(x+3*x^3)``的分析。
       public Expr parseExpr() {
               Expr expr = new Expr();
               int signf = 1;
               //get the first terms
               if (lexer.now().equals("-")) {
                   lexer.next();
                   signf = -1; // the first signal is minus
               } else if (lexer.now().equals("+")) {
                   lexer.next();
               }
               expr.addTerm(parseTerm(signf));
               //get the terms left
               while (lexer.now().equals("-") || lexer.now().equals("+")) {
                   int sign = 1;
                   if (lexer.now().equals("-")) {
                       lexer.next();
                       sign = -1; // the first signal is minus
                   } else {
                       lexer.next();
                   }
                   expr.addTerm(parseTerm(sign));
               }
               return expr;
           }
    

(2)进入 的分析后,我们不难想到 因子 之间是通过乘号分割的,于是同样的第一个 因子 直接调用ParseFactor,随后每读到一个符号之后再调用ParseFactor进入对 因子 的分析。

public Term parseTerm(int sign) {
        Term term = new Term(sign);
        //get the first factor
        term.addFactor(parseFactor());
        //get the left factors
        int signmodi = sign;
        while (lexer.now().equals("*")) {
            lexer.next();
            if (lexer.now().equals("-")) {
                signmodi = signmodi * -1;
                lexer.next();
            } else if (lexer.now().equals("+")) {
                lexer.next();
            }
            term.addFactor(parseFactor());
        }
        term.setSign(signmodi);
        return term;
    }

(3)在对于 因子 的分析中,直接调用ParseFactor 方法。我是将Factor 作为一个父类,子类重写相应的方法,在调用的时候可以直接进入子类对应重写的方法。这里比较难以理解的就是对于 表达式因子 的分析,类似一个递归调用,再次调用分析括号中的 表达式 即可。

  1. 计算化简要完成计算,只是使用我们解析后的表达式是不现实的。因为存在嵌套,解析后并不存在最小计算单元 ,例如想把因子作为最小计算单元的话会发现因子里面还存在表达式,根本无法进行计算。因此必须将解析后的表达式进行转换。我们定义 Poly 类和 Mono 类。其中 Mono 作为最小计算单元: public class Mono {
       private BigInteger coe;
       private int exp;
    
    }

通过对已经解析完毕的表达式中依次调用toPoly()方法

  // Expr
  public Poly toPoly() {
          Poly poly = new Poly();
          for (Term it : terms) {
              Poly temp = poly.addPoly(it.toPoly());
              poly = temp;
          }
          return poly;
      }
  //Term
  public Poly toPoly() {
          Poly poly = new Poly();
          for (Factor it : factors) {
              Poly temp = new Poly();
              if (it instanceof Powerfactor) {
                  Powerfactor xfactor = (Powerfactor) it;
                  temp = poly.mulPoly(xfactor.toPoly());
              } else if (it instanceof Exprfactor) {
                  Exprfactor efactor = (Exprfactor) it;
                  temp = poly.mulPoly(efactor.toPoly());
              } else if (it instanceof Numfactor) {
                  Numfactor cfactor = (Numfactor) it;
                  temp = poly.mulPoly(cfactor.toPoly());
              } // 这里直接调用就可以,不需要进行判断,在第一次作业中还不知道
              poly = temp;
          }
          if (sign == -1) {
              poly.negate();
          }
          return poly;
      }
  //Factor
  //...

在成功解析后,再对调用 PolytoString 方法即可。

复杂度分析

MethodCogCev(G)iv(G)v(G)
Expr.Expr()0111
Expr.addTerm(Term)0111
Expr.toPoly()1122
"Exprfactor.Exprfactor(int, Expr)"0111
Exprfactor.toPoly()0111
Factor.Factor(int)0111
Factor.getExp()0111
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()4234
Lexer.now()0111
MainClass.main(String[])2122
"Mono.Mono(BigInteger, int)"0111
Mono.getCoe()0111
Mono.getExp()0111
Mono.setCoe(BigInteger)0111
Mono.toString()8666
"Numfactor.Numfactor(int, BigInteger)"0111
Numfactor.toPoly()0111
Parser.Parser(Lexer)0111
Parser.parseExpr()7166
Parser.parseFactor()13377
Parser.parseTerm(int)4144
Poly.Poly()0111
"Poly.addMono(int, Mono)"2122
Poly.addPoly(Poly)8166
Poly.mulPoly(Poly)12266
Poly.negate()3133
Poly.powPoly(int)3323
Poly.toStirng()2731011
Powerfactor.Powerfactor(int)0111
Powerfactor.toPoly()0111
Processor.delBlank(String)4134
Processor.processSymbol(String)2155
Term.Term(int)0111
Term.addFactor(Factor)0111
Term.setSign(int)0111
Term.toPoly()6166

第二次作业

UML类图

img

代码架构思路分析

第二次作业增加了exp和自定义函数。

  1. 针对自定义函数,我们新建 FuncProcessFuncfactor对自定义函数的处理,简单来说就是将自定义函数转换为普通因子。我采用的方法是字符串替换。
    (1)预处理部分添加自定义函数,便于后续替换
       int num = Integer.parseInt(scanner.nextLine());
               for (int i = 0; i < num; i++) {
                   String inputfunc = Processor.delBlank(scanner.nextLine());
                   inputfunc = inputfunc.replace("exp", "e");
                   Funcprocess.addFunc(inputfunc);
               }
       
       // FuncProcess
       private static HashMap<String, String> funcMap = new HashMap<>(); // 存储自定义函数表达式
       private static HashMap<String, ArrayList<String>> paraMap = new HashMap<>(); // 存储形参和实参的对应
       
       public static void addFunc(String input) {
               String name = String.valueOf(input.charAt(0));
               funcMap.put(name, input.split("=")[1]);
               int startindex = input.indexOf('(');
               int endindex = input.indexOf(')');
               String paraget = input.substring(startindex + 1, endindex);
               ArrayList<String> paralist = new ArrayList<>(Arrays.asList(paraget.split(",")));
               paraMap.put(name, paralist);
           }
    
    (2)解析函数因子
       //Funcfactor
       
       private String newFunc; //将函数实参带入形参位置后的结果(字符串形式)
       private Expr expr; //将newFunc解析成表达式后的结果
       
       public Funcfactor(String name, ArrayList<Factor> actualparas) {
               super(BigInteger.ONE);
               this.newFunc = Funcprocess.callFunc(name, actualparas);
               this.expr = toExpr();
           }
       
       public static String callFunc(String name, ArrayList<Factor> actualParas) {
                  String func = funcMap.get(name);
                  ArrayList<String> paraList = paraMap.get(name);
                  Map<String, String> replacementMap = new LinkedHashMap<>();
                  for (int i = 0; i < paraList.size(); i++) {
                      if (actualParas.get(i).toPoly().toString().isEmpty()) {
                          replacementMap.put(paraList.get(i), "0");
                      } else {
                          replacementMap.put(paraList.get(i), actualParas.get(i).toPoly().toString());
                      }
                  }
                  String[] orderedParams = {"x", "y", "z"};
                  for (String param : orderedParams) {
                      if (replacementMap.containsKey(param)) {
                          func = func.replace(param, "(" + replacementMap.get(param) + ")");
                      }
                  }
                  func = Processor.processSymbol(func);
                  return func;
              }
    

这里要注意,字符串替换的时候会产生一些问题:

  • exp中含有x,如果对所有的x直接替换成实参的话会出错。因此把exp转化成e
  • x是一个特殊的参数,如果出现f(y,x) = x + 2的情况,只按照参数出现的顺序进行调用就会出问题,所以最好按照xyz的顺序替换
  1. 针对exp

主要的难点出在之后的计算,由于新增了exp,导致多项式相加时能否合并的判断变得复杂了很多。我在第二次作业中新增了 Pair类进行判断,同时重写了Poly.equals()方法。

public class Pair {
    private BigInteger varExp;
    private BigInteger expExp;
    private Poly expPoly;
    private Poly wholeExpPoly;

    public Pair(BigInteger varExp, BigInteger expExp, Poly expPoly) {
        this.varExp = varExp;
        this.expPoly = expPoly;
        this.expExp = expExp;
        if (expPoly != null) {
            if (!expPoly.isPolyEmpty()) {
                this.wholeExpPoly = expPoly.mulPoly(Poly.getConstPoly(expExp));
            } else {
                this.wholeExpPoly = expPoly.getEmptyPoly();
            }
        } else {
            System.out.println("expPoly is null");
        }
    }

在对Pair的判等中又涉及到对Poly的判等

@Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Poly poly = (Poly) o;
        if (monoMap.size() != poly.monoMap.size()) {
            return false;
        }

        for (Pair key : monoMap.keySet()) {
            if (!poly.monoMap.containsKey(key)) {
                return false;
            }
            ArrayList<Mono> thisList = monoMap.get(key);
            ArrayList<Mono> thatList = poly.monoMap.get(key);
            if (thisList.size() != thatList.size() ||
                    !thisList.containsAll(thatList) || !thatList.containsAll(thisList)) {
                return false;
            }
        }

        return true;
    }

这里对Poly的判等还可以通过调用toString方法比较字符串来实现,但这样反复地递归调用很可能会TLE,因此本人采用了分析内部结构的办法。这个方法虽然大大降低了时间复杂度,但由于我的一些处理不到位,在后续带来了一些问题,虽然修改是在第三次作业的重构中完成的,但是由于是第二次作业的内容,我就把它放到这里来讲。

重构

这个问题就是:对0的处理

Poly为0的时候,它应该是什么样呢?是空的,还是能够有Mono但是Momo全都为0呢?

在我对Poly的判等中,采用了返回size是否相等的办法,这就意味着我只能采用第一种办法。因为如果Poly1中有一个Mono为1,而Poly2中有一个为1的Mono和一个为0的Mono,我的程序会判断他们不相等。这就造成了合并同类项困难的问题。

于是在后续的程序中,我统一将空的Poly视为0。同时在加乘中对0进行特判来简化逻辑。并为了防止大家提到的深浅拷贝问题,在加减中统一进行深拷贝,返回新的Poly

此处Mono Poly Pair三者形成了类似递归的拷贝,我们将0设为终点。

拷贝参考代码如下:

// pair
public static Pair deepCopyPair(Pair another) {
        Poly expPoly = another.expPoly.isPolyZero() ?
                Poly.getZeroPoly() : Poly.deepCopyPoly(another.expPoly);
        return new Pair(another.varExp, another.expExp, expPoly);
 }
// Mono
public static Mono deepCopyMono(Mono another) {
        Poly expPoly = another.expPoly.isPolyZero() ?
                Poly.getZeroPoly() : Poly.deepCopyPoly(another.expPoly);
        return new Mono(another.coe, another.varExp, another.expExp, expPoly);
}
// Poly
public static Poly deepCopyPoly(Poly another) {
        Poly clonePoly = new Poly();
        for (Map.Entry<Pair, ArrayList<Mono>> entry : another.monoMap.entrySet()) {
            Pair pair = Pair.deepCopyPair(entry.getKey());
            ArrayList<Mono> monoList = new ArrayList<>();
            for (Mono mono : entry.getValue()) {
                monoList.add(Mono.deepCopyMono(mono));
            }
            clonePoly.monoMap.put(pair, monoList);
        }
        return clonePoly;
    }

具体加和乘的计算这里不再赘述。

关于重构的体验,我实际上是在第三次作业完成通过中测后才进行的重构。当时本来想着,中测都过了,也已经是最后一次迭代,或许不再有重构的必要了。但是看着运行时一大堆000000和最后没有被完全合并的式子,还是不甚满意,进行了上述重构。

实际上,有了完整正确思路的重构并不像想象中那么复杂,我只花了一个下午就完成了。因此,面对不完美的代码,重构是势在必行的。关键是在重构之前必须有亟待解决的问题和完整的思路,否则很可能在重构的过程中就不知道自己在干什么了。

复杂度分析

MethodCogCev(G)iv(G)v(G)
Expfactor.Expfactor(BigInteger, Expr)0111
Expfactor.toPoly()0111
Expr.Expr()0111
Expr.addTerm(Term)0111
Expr.toPoly()1122
Exprfactor.Exprfactor(BigInteger, Expr)0111
Exprfactor.toPoly()0111
Factor.Factor(BigInteger)0111
Factor.getExp()0111
Factor.toPoly()8555
Funcfactor.Funcfactor(String, ArrayList)0111
Funcfactor.toExpr()0111
Funcfactor.toPoly()0111
Funcprocess.addFunc(String)0111
Funcprocess.callFunc(String, ArrayList)7155
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()7267
Lexer.now()0111
MainClass.main(String[])6144
Mono.Mono(BigInteger, BigInteger, BigInteger, Poly)5133
Mono.addCoe(BigInteger)0111
Mono.equals(Object)4346
Mono.findClosingBracket(String, int)7535
Mono.getCoe()0111
Mono.getExpExp()0111
Mono.getExpPoly()0111
Mono.getVarExp()0111
Mono.getWholeExpPoly()0111
Mono.hashCode()0111
Mono.isFactor(String)9579
Mono.mulMono(Mono, Mono)3223
Mono.setCoe(BigInteger)0111
Mono.toString()2271115
Numfactor.Numfactor(BigInteger, BigInteger)0111
Numfactor.toPoly()0111
Pair.Pair(BigInteger, BigInteger, Poly)5133
Pair.equals(Object)4335
Pair.getExpExp()0111
Pair.getExpPoly()0111
Pair.getVarExp()0111
Pair.getWholeExpPoly()0111
Pair.hashCode()0111
Parser.Parser(Lexer)0111
Parser.isNum(String)3323
Parser.parseExpr()7166
Parser.parseExprFactor()3133
Parser.parseFactor()2161212
Parser.parseFuncFactor()1122
Parser.parseTerm(int)4144
Poly.Poly()0111
Poly.addAllMono()10367
Poly.addMono(Pair, Mono)2122
Poly.addPoly(Poly)1211010
Poly.equals(Object)107510
Poly.getConstPoly(BigInteger)0111
Poly.getEmptyPoly()0111
Poly.hashCode()0111
Poly.isPolyEmpty()0111
Poly.mulPoly(Poly)12266
Poly.negate()3133
Poly.polyString(ArrayList<Mono>)263910
Poly.powPoly(BigInteger)3323
Poly.toString()1122
Powerfactor.Powerfactor(BigInteger)0111
Powerfactor.toPoly()0111
Processor.delBlank(String)4134
Processor.processSymbol(String)2155
Term.Term(int)0111
Term.addFactor(Factor)0111
Term.setSign(int)0111
Term.toPoly()8188

第三次作业中,方法复杂度高的方法基本上都是toString方法,由于需要优化和括号处理而进行的许多特判产生的高复杂度,本人认为并不容易降低,所幸也没产生bug,那也就无所谓了。


第三次作业:

UML类图

img

代码架构思路分析

第三次作业比较简单,求导功能的实现只需遵循求导法则对每个解析存储类写一个求导方法即可。

// Expr
public Poly todxPoly() {
        Poly poly = new Poly();
        for (Term it : terms) {
            Poly temp = poly.addPoly(it.todxPoly());
            poly = temp;
        }
        return poly;
}
//Term
public Poly todxPoly() {
        Poly poly = new Poly();
        for (int i = 0; i < factors.size(); i++) {
            Poly temp = Poly.getConstPoly(BigInteger.ONE);
            for (int j = 0; j < factors.size(); j++) {
                if (i != j) {
                    temp = temp.mulPoly(factors.get(j).toPoly());
                } else {
                    temp = temp.mulPoly(factors.get(j).todxPoly());
                }
            }
            poly = poly.addPoly(temp);
        }
        if (sign == -1) {
            poly.negate();
        }
        return poly;
}
//Defactor
public Poly todxPoly() {
        Poly poly1 = expr.todxPoly().powPoly(super.getExp());
        String exprString = poly1.toString();
        Expr expr1 = new Expr();
        if (exprString.isEmpty()) {
            expr1 = toExpr("0");
        } else {
            expr1 = toExpr(exprString);
        }
        Poly poly = expr1.todxPoly().powPoly(super.getExp());
        return poly;
    }

复杂度分析

MethodCogCev(G)iv(G)v(G)
Defactor.Defactor(BigInteger, Expr)0111
Defactor.toExpr(String)0111
Defactor.toPoly()0111
Defactor.todxPoly()2122
Expfactor.Expfactor(BigInteger, Expr)0111
Expfactor.toPoly()0111
Expfactor.todxPoly()1222
Expr.Expr()0111
Expr.addTerm(Term)0111
Expr.toPoly()1122
Expr.todxPoly()1122
Exprfactor.Exprfactor(BigInteger, Expr)0111
Exprfactor.toPoly()0111
Exprfactor.todxPoly()0111
Factor.Factor(BigInteger)0111
Factor.getExp()0111
Factor.toPoly()0111
Factor.todxPoly()0111
Funcfactor.Funcfactor(String, ArrayList)0111
Funcfactor.toExpr()0111
Funcfactor.toPoly()0111
Funcfactor.todxPoly()0111
Funcprocess.addFunc(String)0111
Funcprocess.callFunc(String, ArrayList<Factor>)7155
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.next()8278
Lexer.now()0111
MainClass.main(String[])6144
Mono.Mono(BigInteger, BigInteger, BigInteger, Poly)6144
Mono.addCoe(BigInteger)0111
Mono.deepCopyMono(Mono)1122
Mono.equals(Object)4346
Mono.findClosingBracket(String, int)7535
Mono.getCoe()0111
Mono.getExpExp()0111
Mono.getExpPoly()0111
Mono.getVarExp()0111
Mono.getWholeExpPoly()0111
Mono.hashCode()0111
Mono.isFactor(String)5377
Mono.isMonoZero()0111
Mono.mulMono(Mono, Mono)3223
Mono.setCoe(BigInteger)0111
Mono.toString()2771417
Numfactor.Numfactor(BigInteger, BigInteger)0111
Numfactor.toPoly()1122
Numfactor.todxPoly()0111
Pair.Pair(BigInteger, BigInteger, Poly)6144
Pair.deepCopyPair(Pair)1122
Pair.equals(Object)4335
Pair.getExpExp()0111
Pair.getExpPoly()0111
Pair.getVarExp()0111
Pair.hashCode()0111
Parser.Parser(Lexer)0111
Parser.isNum(String)3323
Parser.parseExpr()7166
Parser.parseExprFactor()5144
Parser.parseFactor()7777
Parser.parseFuncFactor()1122
Parser.parseTerm(int)4144
Parser.processPow(Lexer)5244
Poly.Poly()0111
Poly.addAllMono()10367
Poly.addMono(Pair, Mono)2122
Poly.addPoly(Poly)3031414
Poly.deepCopyPoly(Poly)3133
Poly.divconstPoly(Poly, BigInteger)3133
Poly.equals(Object)107510
Poly.getConstPoly(BigInteger)1222
Poly.getGcd()10445
Poly.getZeroPoly()0111
Poly.hashCode()0111
Poly.isPolyZero()0111
Poly.mulPoly(Poly)16288
Poly.negate()3133
Poly.polyString(ArrayList)263910
Poly.powPoly(BigInteger)4334
Poly.toString()1122
Powerfactor.Powerfactor(BigInteger)0111
Powerfactor.toPoly()0111
Powerfactor.todxPoly()1222
Processor.delBlank(String)4134
Processor.processSymbol(String)2155
Term.Term(int)0111
Term.addFactor(Factor)0111
Term.setSign(int)0111
Term.toPoly()2133
Term.todxPoly()8155

测试

生成测试数据时,我们仍然使用递归下降的思想:

    import random
    import sympy
    
    RANGE = 5
    Addsign = 0
    symbol = "+-*^x"
    def gener_E(flag): # 表达式是否在括号内
        if flag:
            s = gener_T(True)
            num = random.randint(0, RANGE)
            for i in range(num):
                s += "+" + gener_T(True)
        else:
            s = gener_T(False)
            num = random.randint(0, RANGE)
            for i in range(num):
                s += "+" + gener_T(False)
        return s
    def gener_T(flag):
        if flag:
            s = gener_F(True)
            num = random.randint(0, RANGE)
            for i in range(num):
                s += "*" + gener_F(True)
        else:
            s = gener_F(False)
            num = random.randint(0, RANGE)
            for i in range(num):
                s += "*" + gener_F(False)
        return s
    
    def gener_F(flag):
        F_list = [gener_F_num, gener_F_pow, gener_F_expr]  # 函数列表
        if flag:
            choice = random.randint(0, len(F_list) - 2)
        else:
            choice = random.randint(0, len(F_list) - 1)
        return F_list[choice]()
    
    def gener_F_num():
        num = random.randint(-11, 11)
        return str(num)
    
    def gener_F_pow():
        exp = random.randint(0, 8)
        return "x^+" + str(exp)
    
    def gener_F_expr():
        exp = random.randint(0, 8)
        return "("+gener_E(True)+")^" + str(exp)
    
    def addZero(s):
        new_strng = ""
        for i in range(len(s)):
            if i > 0 and s[i-1] in symbol and s[i].isdigit():
                num_zeros = random.randint(1, 3)
                zeros = "0" * num_zeros
                new_strng += zeros + s[i]
            else:
                new_strng += s[i]
        return new_strng
    
    def getBlank():
        blank = random.randint(0,  1)
        return " " if blank == 0 else "\t"
    
    def addBlank(s):
        new_string = ""
        add = random.randint(0, 2)
        for i in range(len(s)):
            if i>0 and not s[i-1].isdigit() and not s[i].isdigit():
                new_string += s[i] if add < 2 else getBlank() + s[i]
            else:
                new_string += s[i]
        return new_string
    
    s = gener_E(False)
    s_add = addZero(s)
    s_complete = addBlank(s_add)
    print(s)
    print(len(s))
    print(s_complete)
    print(len(s_complete))

bug分析

第二次互测在输出时没有对exp的0进行特判导致错误

主要出错原因是在编写Mono.toString 方法的时候没有考虑周全,也没有进行充足的测试。


优化

第一次作业:

  1. 正项提前
  2. -1*x -> -x

第二次作业:

在第一次作业优化的前提下,添加提取公因数和判断是否需要双括号

String expr = this.wholeExpPoly.toString();
            BigInteger gcd = this.wholeExpPoly.getGcd();
            if (isFactor(expr)) {
                expString = "e(" + expr + ")";
            } else if (gcd.compareTo(BigInteger.ONE) != 0) {
                Poly gcdPoly = Poly.divconstPoly(this.wholeExpPoly, gcd);
                if (isFactor(gcdPoly.toString())) {
                    expString = "e(" + gcdPoly.toString() + ")" + "^" + gcd.abs();
                } else {
                    expString = "e((" + gcdPoly.toString() + "))" + "^" + gcd.abs();
                }
            } else {
                expString = "e((" + expr + "))";
            }

心得体会

如果要用一句话概括这一个月的迭代,那就是“痛并快乐着”。既痛苦于复杂的迭代,痛苦于初探索时混乱的代码,也快乐于一月以来的进步,惊喜于重构后的柳暗花明。从一月之前还不明白什么是递归下降,到现在已经可以写出解析表达式的复杂代码,痛苦挣扎的背后是肉眼可见的进步。在随后的三个月,我们还将继续迎接疼痛,也期待着疼痛背后的欣喜和快乐。

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

301

社区成员

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

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