BUAA_OO_2021_第一单元总结

一、程序结构

1)第一次作业

本次作业我使用了两个类:Polynomial (主类) 和 Item ,其中 Item 类表示一个,其属性保存了一个项的系数和幂函数x的指数,Item 通过正则表达式解析一个项的字符串,提取其中的系数和指数,并进行求导计算,最后再将求导后的结果转化成字符串;Polynomial 是主类,完成了输入表达式字符串的读取,定义 Item 数组保存表达式各个项的信息,预处理输入字符串,同类项合并,化简(即将项数组中系数为0的项去掉)等工作,最后输出结果。

Item


Item 类有三个属性:

  • private String value

    存储输入的一个项的字符串

  • private BigInteger coefficient

    为该项的系数,初始化为0

  • private BigInteger exponent

    表示该项幂函数的指数,初始化为1


Item 类有七个方法:

  • public Item(String value) (4 行)

这是构造方法,读入一个项的字符串,并调用求导函数 derivation 进行求导。

  • public void derivation() (38 行)

这是对一个项的求导方法,由于面向对象思想还不丰富,这个方法其实杂糅了正则解析,保存数据和求导三个功能,通过使用正则表达式,对 value 进行解析,匹配并解析每一个因子,若因子是系数,则与本地系数相乘;若因子是指数,则与本地指数相加。匹配结束后,对该项进行求导,即根据求导规则,修改该项的系数和指数。

  • public String toString() (29 行)

重写了 toString 方法,返回该项的字符串形式

  • 4个 getters and setters 方法

 

Polynomial 主类


Polynomialmain函数:

  • 变量定义:

定义动态数组 ArrayList<Item> itemlist = new ArrayList<>() 用来保存每一个项的数据

  • 预处理策略:

首先,将所有的空白字符去掉

将所有的 ** 改为 ^ ,以区分乘法和乘方

将所有连续的两个加减符号替换为一个

(由于后来测试阶段发现无法读取 -x 前面的负号,)将所有的 -x 改为 -1*x

  • 解析输入字符串策略:

写出一个项的正则表达式,用该正则去匹配每一个项,将每一个匹配到的项存入项动态数组中

项的正则表达式定义如下:

String regInt = "(((\\+)|(-))?(\\d)(\\d)*)"; // 带符号整数的正则表达式

String regPower = "(x\\^)" + regInt; // 带指数的幂函数的正则表达式

String regX = "(x)"; // 不带指数的幂函数的正则表达式

String regFactor = "(" + regInt + "|" + regPower + "|" + regX + ")"; // 因子的正则表达式

String regItem = regFactor + "(\\*" + regFactor + ")*"; // 项的正则表达式

  • 优化策略:

调用静态方法 combine 合并同类项,并去掉所有系数为0的项

  • 输出结果:

调用静态方法 print 输出最终结果


Polynomial 有两个静态方法:

  • public static void combine(ArrayList<Item> itemlist) (26 行)

该方法合并同类项(即指数相同的项系数相加),并将系数为0的项从动态数组中去掉

  • public static void print(ArrayList<Item> itemlist) (16 行)

该方法将优化后的项数组以字符串形式输出,如果数组为空,则直接输出0;如果第一个项的符号为+,则不输出+;其他情况直接输出项的字符串形式

 

2)第二次作业

本次作业我使用了三个类:MainExpressionTermCalculation

本次作业我的大体思路是将括号展开,然后将每一个项看成一个由系数、幂函数、sin和cos组成,然后进行求导计算。在优化方面,我仍然使用了第一次作业中的合并同类项和去掉系数为0的项这两种优化策略。在合并同类项时我使用了 HashMap 这个数据结构,用指数作为Key值,每次获得新的项时,就在Map中寻找是否存在指数相同的项,并进行合并或添加操作,但是本题中一个项有三个指数,三个指数不可能同时作为Key值,因此我将三个指数用空格分隔表示成一个字符串,以该字符串作为一个项的“指数”。

Term

该类用来保存一个项的信息,并实现了一些有关项的方法


Term 类有四个属性:

  • private BigInteger coe

表示该项的系数,初始为1

  • private BigInteger expX

表示该项幂函数的指数,初始为0

  • private BigInteger expS

表示该项sin函数的指数,初始为0

  • private BigInteger expC

表示该项cos函数的指数,初始为0


Term 类有5+8个方法,其中8个是 getters and setters

  • public Term(BigInteger coe, BigInteger expX, BigInteger expS, BigInteger expC)(6行)

这是构造方法,初始化成员变量

  • public Term multiply(Term term) (7 行)

这个方法实现了两个项相乘,并返回一个新的项表示相乘的结果

  • public String getIndex() (3 行)

这个方法返回一个项的“系数”,即由空格隔开的三个指数的字符串

  • public String toString() (32 行)

返回一个项带符号的字符串形式

  • public String funToString(BigInteger expX, BigInteger expS, BigInteger expC, String str) (34 行)

返回这个项的函数的字符串形式,由于之前的 toString 方法太长,故将其分为两部分,这个方法用于生成最终结果的函数字符串部分。

 

Expression

该类是定义了一个 HashMap 用来保存一个表达式的各个项,实现了解析输入字符串并提取每个项的信息的功能,并将最终结果输出


Expression 类有一个成员变量:

  • private HashMap<String, Term> expression = new HashMap<String, Term>();

用一个 HashMap 保存所有项,其中Key值表示一个项的“指数”,即 expX + “ ” + expS + " " + expC

 

Expression 类一共有十五个方法


Expression 类有三个构造方法:

  • public Expression()

构造一个不存在项的 Expression 对象

  • public Expression(HashMap<String, Term> expression)

构造一个 Expression 对象,并初始化它的项列表

  • public Expression(Term term)

构造一个 Expression 对象,并将 term 加入到它的项中

 


Expression 类有四个方法,用于合并表达式,乘上新的表达式,去掉0项,输出结果

  • public void combine(Expression expA)(12 行)

将新的表达式与自身表达式进行合并

  • public void multiply(Expression expA)(4 行)

将自身表达式乘上新的表达式

  • public void cleanZero() (9 行)

去掉自身表达式中系数为0的项

  • public void print() (19 行)

输出最终结果

 


Expression 类有七个方法,用于解析输入字符串并匹配每个因子

  • public void getMap(String str) (20 行)

循环匹配每个项,并将每个项的信息保存在项列表中,定义一个新的子表达式用于保存每次新匹配到的项/括号表达式,每次匹配完一个项之后,将这个表达式加入到这个类的表达式中

  • public static String match(Expression subE, String str)(48行)

  • public static String matchX(String str) (10行)

  • public static String matchS(String str) (10行)

  • public static String matchC(String str) (10行)

  • public static String matchB(String str) (16行)

  • public static String matchI(String str) (7行)

这是用于匹配一个项并解析其参数的方法组合,匹配策略为:

判断第一个字符,若为 x 则是幂函数,若为 s 则是 sin 函数,若为 c 则是 cos 函数,若为 ( 则是括号表达式,其余情况则是整数因子

然后调用相应的亚方法匹配出对应因子的字符串,并返回该因子字符串

解析该因子字符串,并将这个因子的信息保存到一个新表达式中,然后调用 multiply 方法将子表达式乘上新表达式,并从原字符串中删掉该字符串

 

Calculation

该类用于实现表达式相乘和求导的功能


Calculation 类有两个方法:

  • public static Expression multiply(Expression expA, Expression expB)(25行)

该方法实现了两个表达式相乘的功能,返回一个表达式,表示最终结果

  • public static Expression derivation(Expression expA) (42行)

该方法实现了对一个表达式求导

 

Main 主类

主要实现了对输入字符串的读入,预处理,定义一个表达式实例用于解析输入字符串并求导,最后输出结果


Main 类有一个静态方法:

  • public static String init(String str)

对输入字符串进行预处理

 

3)第三次作业

本次作业我使用了六个类:SinCosTermExpressionWrongFormatExceptionPolyNomial

本次作业我仍是把每个表达式看成项的集合,每个项五个成员变量:系数,x的指数,sin函数表,cos函数表,表达式表,三角函数的成员变量包括指数和由一个因子组成的项。由于这次作业增加了错误格式判断,因此不能直接用正则表达式去匹配每个因子,我采用的方法是每个字符逐个读取判断,在匹配因子时,如果格式正确则用正则表达式匹配读取因子,否则抛出异常。

Sin

存储 sin 函数的信息,实现其求导方法和转换成字符串的方法


sin 类有两个成员变量:

  • private int exp

三角函数的指数

  • private Term term

三角函数括号内的因子,只包含一个因子的项

 


sin 类有两个构造方法:

  • public Sin()

  • public Sin(int exp, Term term)

 


sin 类有两个3+2个方法,其中3个 getters and setters 方法:

  • public Term derivation() (9行)

实现三角函数的求导

  • public String toString() (13行)

转换成字符串

 

Cos类

存储 cos 函数的信息,实现其求导方法和转换成字符串的方法


cos 类有两个成员变量:

  • private int exp

三角函数的指数

  • private Term term

三角函数括号内的因子,只包含一个因子的项

 


cos 类有两个构造方法:

  • public Cos()

  • public Cos(int exp, Term term)

 


cos 类有两个3+2个方法,其中3个 getters and setters 方法:

  • public Term derivation() (9行)

实现三角函数的求导

  • public String toString() (13行)

转换成字符串

 

Term

用于保存一个项的信息,实现了项的求导方法


Term 类有五个成员变量:

  • private BigInteger coe

系数,初始化为1

  • private int exp

x函数的指数,初始化为0

  • private ArrayList<Sin> sin

sin 函数列表,初始化为空列表

  • private ArrayList<Cos> cos

cos 函数列表,初始化为空列表

  • private ArrayList<Expression> expression

括号表达式列表,初始化为空列表

 


Term 类有两个构造方法:

  • public Term()

  • public Term(BigInteger coe, int exp, ArrayList<Sin> sin, ArrayList<Cos> cos, ArrayList<Expression> expression)

 


Term 类有五个方法,用于添加成员变量:

  • public void addCoe(BigInteger coe)

  • public void addExp(int exp)

  • public void addSin(Sin sin)

  • public void addCos(Cos cos)

  • public void addExpression(Expression expression)

 


Term 类有一个方法,用于求导:

  • public Expression derivation() (60行)

该方法用于对一个项进行求导,对一个有多个因子的项求导,返回值为一个表达式

 


Term 类有三个方法,用于返回该项的字符串

  • public String toString()

  • public boolean isConstant()

  • public String xtoString()


此外,Term 类还有六个 getters and setters 方法

 

Expression

该类用于解析输入字符串,逐个字符匹配,并判断非法格式


Expression 类有一个成员变量:

  • private ArrayList<Term> expression

保存一个表达式的每个项

 


Expression 类有一个构造方法:

  • public Expression()

 


Expression 类有两个方法,用于添加成员变量:

  • public void addTerm(Term term)

  • public void addExpression(Expression expression)

 


Expression 类有十二个方法,用于解析输入字符串,匹配因子,判断格式:

  • public void parse(String str0) throws WrongFormatException(25行)

  • public static String preTerm(String str) throws WrongFormatException(49行)

  • public static String match(String str0, Term term) throws WrongFormatException(23行)

  • public static String matchSigned(String str0, Term term) throws WrongFormatException(11行)

  • public static String matchUnsigned(String str0, Term term) throws WrongFormatException(8行)

  • public static String matchX(String str0, Term term) throws WrongFormatException(50行)

  • public static String matchB(String str0, Term term) throws WrongFormatException(8行)

  • public static String getBracket(String str0) throws WrongFormatException(21行)

  • public static String matchS(String str0, Term term) throws WrongFormatException(23行)

  • public static String getSinExp(String str0, Sin sin) throws WrongFormatException(50行)

  • public static String matchC(String str0, Term term) throws WrongFormatException(23行)

  • public static String getCosExp(String str0, Cos cos) throws WrongFormatException(50行)

循环匹配每个项,匹配每个项之前,调用 preTerm 方法预处理字符串,将项前面的空格去掉,若存在符号则把可能的多个符号处理成一个符号;在匹配每个项时,再循环匹配每个因子,即判断第一个字符属于哪种因子,然后调用对应因子的匹配方法读取该因子信息,并将读取过的字符串从原字符串中去掉,其中,匹配括号表达式和三角函数的底数时递归调用了表达式类的 parse 方法匹配子表达式。在上面的过程中,如果存在格式非法,则立马抛出异常。

 


Expression 类有一个方法,用于求导:

  • public Expression derivation()(7行)

对表达式的每一个项调用求导方法进行求导

 


Expression 类有一个方法,用于输出最终结果

  • public String toString()(9行)

 

WrongFormatException

有一个方法,用于打印错误信息:

  • public void printExceptionInfomation()

 

Polynomial

读取输入字符串,定义表达式对象,对输入字符串进行处理,求导,并输出最终结果,同时捕获异常。

4)优缺点自我评价

优点:将表达式进行层次分解,每个层次写一个类,分别管理各个层次对应的数据,实现相应的方法,在一定程度上降低了程序的耦合性。

缺点:三次作业中我的类使用都特别复杂,将很多方法杂糅到一个类中,导致程序不够简洁明确,各个类之间的关系比较大,导致耦合性比较高,由于有些功能(如字符串匹配,toString 等)实现方法比较繁琐,导致对应的方法规模太大,不得不分解成数个子方法,这就导致了类方法之间的耦合性高,独立性差。

 

二、自己程序的bug

第一次作业有一个bug:在最终输出结果时乘方符号 ** 少了一个 *

第二次作业没有发现bug

第三次作业有两个bug:

  • 并未考虑括号表达式以及三角函数的底数为空串时应该抛出异常

  • 输出最终结果时将三角函数底数的第一个字符吞掉,导致输出结果错误

三、发现别人bug时采用的策略

组合了所有基本功能的样例

极端情况的样例,比如输入为0,x-x,sin(((()))等

阅读同学的代码,根据代码功能反向构造样例

四、重构经历总结

第二次作业由于三角函数底数只能是x,所以我将所有括号展开,这样整个表达式就变成了项的集合,而每个项有统一的形式,因此第二次作业就是在第一次作业的基础上,增加了递归读取括号,并将括号展开的功能,之后再按照第一次作业的方式,将每个项依次求导。

第三次作业三角函数的底数变成了因子,所以就算我将所有括号展开了,真个表达式仍然不能像第二次作业那样有统一的形式,(即使有,那也是非常复杂的,因为每个项的三角函数因子底数可能不同,所以每个项仍然需要三角函数列表)因此第三次作业我几乎是重写了整个代码,项类和三角函数类相互调用,表达式类和项类也相互调用,并没有进行化简操作。

五、心得体会

通过本单元作业,我深深感受到了OO课程的一大特点:架构才是写好一个程序最重要的。第二次和第三次作业在动手写代码之前,我都花了几乎两三个小时去构思整个代码架构,包括如何将一个表达式进行层次解构,如何设计类来管理每个层次的数据,如何管理各层次数据之间的关系,如何匹配一个字符串,提取其中的因子信息。此外,我也对面向对象编程思想有了一定的认识,将所有的数据看成对象的属性,将具有相同特点的一些数据封装到一个类中,将管理使用这些数据的功能写成方法,封装到类中,实现程序的简介性,封装性,低耦合性,使得问题的解决更加简便。

上一篇:Typescript-07-if 条件语句


下一篇:精心整理了一套SQL Server常用函数,速速收藏!