Summary of Object-Oriented Design and Construction Unit 1
Introduction
The overall training objective of the first unit assignments is to model expression structures, experience hierarchical design thinking, and develop iteratively. The three assignments focus on expanding polynomial expressions in the first task, introducing trigonometric functions (sin, cos) and custom functions (f(x), g(x,y), h(x,y,z)) in the second task, and adding differentiation factors (dx(expression)) and allowing function definitions to call other predefined functions in the third task.
You can refer to my code here: GitHub Repository
Summary of First Assignment
Design and Architecture
My general approach was divided into three steps:
- Preprocessing the expression
- Parsing the expression
- Converting to a string and printing
Preprocessing Expressions
PreStr
Preprocessing includes:
- Removing all consecutive whitespace characters using Java’s built-in
replaceAll
method. - Using a series of
replaceAll
calls to ensure that the string does not contain two or more consecutive ‘+’ or ‘-‘ signs (e.g., “—-“ should be simplified to “+”, “+-“ to “-“). - Replacing the power symbol “**” with “^” to avoid confusion with multiplication symbols and prevent unnecessary bugs.
Parsing Expressions
Lexer
Processing involves:
read()
ensures that each operand or operator is retrieved at a time.next()
reads the next operand or operator.- Due to the unpredictable length of numbers,
getNumber()
retrieves the current number.
Ele
According to the requirements, I created an Ele
class for storing basic elements. Each Ele
has the following structure:
coe
stores the coefficient of eachEle
.hashVar
uses aHashMap
to store variables and their corresponding powers.
Initially, only setCoe()
, getCoe()
, setHashVar()
, and getHashVar()
methods were written for setting and getting values. Other methods were added during the overall framework construction.
Expr
A sequence of expressions looks like this:
Coefficients may carry signs themselves, so they are directly connected by plus signs.
To facilitate data access, I chose an ArrayList
to store multiple Ele
objects:
The Expr
class contains methods such as initExperList(ArrayList<Ele>)
, initExpr(Ele)
for initialization, and getExperlist()
for retrieving the ArrayList
. Many other methods were completed during the overall construction process.
Parser
Using recursive descent parsing for expressions:
Note: dealEx(Expr)
is part of parseExpr()
, separated due to method length affecting code style.
parseExpr()
parses expressions, which consist of several terms connected by addition and subtraction operators. The first term may have a positive or negative sign indicating its polarity. Thus, it considers the following structure:
1 | public parseExpr() { |
Sign handling is involved, so distinguishing between addition and subtraction within the method isn’t necessary; instead, a new combination method Expr.combTerm(ArrayList<Ele> list, String s)
is used, where s
represents the sign. If s
is a minus sign, Expr.reverseList(ArrayList<Ele> list)
is called to negate, then addAll()
is used. For performance, simplification occurs after combTerm
using merge()
to combine like terms.
parseTerm()
parses terms, which consist of several factors connected by multiplication operators. Similar to above but requires writing a multiplication methodExpr.mulTerm(ArrayList<Ele>)
to multiply twoArrayList<Ele>
objects (essentially multiple additions).parseFactor()
parses factors, which include power functions, constant factors, and expression factors.parsePow()
handles power functions similarly and returns anExpr
object. Separated due to method length affecting code style.
Converting to String and Printing
This primarily manifests in the toString()
method of the Expr
class. The idea is to set up a StringBuilder
and continuously append. Each Ele
in the ArrayList
is sequentially checked for ‘+’, whether it is ±1, if the exponent is 0, and if the whole expression is 0. This section requires special attention to detail.
Program Structure Analysis Based on Metrics
Code Size Analysis
Core code is around 450 lines, with the Expr
class having the most lines at 175, followed by the Parser
class with 119 lines.
Complexity Analysis
Method Complexity
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DeFun.max(int, int) | 0 | 1 | 1 | 1 |
DeFun.process(String) | 22 | 6 | 13 | 13 |
DeFun.readFun(String) | 6 | 3 | 5 | 6 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 5 | 2 | 3 | 6 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 2 | 1 | 3 | 3 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 18 | 5 | 11 | 12 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
Parser.parseTri() | 8 | 1 | 4 | 4 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
expr.Ele.Ele() | 0 | 1 | 1 | 1 |
expr.Ele.canMerge(Ele) | 4 | 1 | 4 | 6 |
expr.Ele.compareTo(Object) | 27 | 13 | 12 | 13 |
expr.Ele.getCoe() | 0 | 1 | 1 | 1 |
expr.Ele.getHashCos() | 0 | 1 | 1 | 1 |
expr.Ele.getHashSin() | 0 | 1 | 1 | 1 |
expr.Ele.getHashVar() | 0 | 1 | 1 | 1 |
expr.Ele.initHashCos(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashSin(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.isOne() | 6 | 1 | 5 | 8 |
expr.Ele.isZero() | 0 | 1 | 1 | 1 |
expr.Ele.putHashCos(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.putHashSin(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashCos(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashSin(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.zero() | 0 | 1 | 1 | 1 |
expr.Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
expr.Expr.getExprList() | 0 | 1 | 1 | 1 |
expr.Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
expr.Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
expr.Expr.merge() | 10 | 4 | 7 | 7 |
expr.Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
expr.Expr.reverse() | 1 | 1 | 2 | 2 |
expr.Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
expr.Expr.simplify() | 4 | 3 | 4 | 4 |
expr.Expr.sort() | 32 | 13 | 12 | 13 |
expr.Expr.toString() | 16 | 1 | 10 | 11 |
expr.Expr.triStr(Ele) | 14 | 1 | 11 | 11 |
expr.Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
expr.Term.getTermList() | 0 | 1 | 1 | 1 |
Total | 233 | 90 | 180 | 195 |
Average | 4.75 | 1.83 | 3.67 | 3.97 |
Class Complexity
The diagram shows that the most complex methods are Parser.parseFactor()
, expr.Expr.toString()
, and expr.Expr.varStr(Ele)
. The complexity increases significantly for these methods because they handle multiple types of factors and string conversion scenarios.
The diagram also highlights that the Expr
, Ele
, and Parser
classes have higher complexity due to the numerous methods for decomposition and calculation/merging.
Attached: Metric Analysis Item Explanations
- CogC: Cognitive Complexity
- ev(G): Basic complexity of non-abstract methods, measures control flow structure defects, range [1, v(G)]
- iv(G): Design complexity of methods, measures coupling between method control flows and other methods, range [1, v(G)]
- v(G): Cyclomatic complexity of non-abstract methods, measures the number of different execution paths within each method
- OC: Non-abstract method cyclomatic complexity of classes, excluding inherited classes
- WMC: Total cyclomatic complexity of classes
UML Class Diagram
Each class design consideration has been explained in the previous sections, so it will not be repeated here.
Summary of Second Assignment
Design and Architecture
New requirements for this assignment included:
- Supporting nested multi-level parentheses.
- Adding trigonometric factor support, with any factor inside trigonometric function parentheses.
- Introducing custom function factors, though custom function expressions do not call other functions.
Based on the first assignment, the following tasks needed to be accomplished:
- Replace custom functions.
- Introduce new data structures to store trigonometric factors.
- Add or improve methods to complete trigonometric function calculations and simplifications.
Replacing Custom Functions
DeFun
Implementation of replacement allows for expression parsing using the previously implemented recursive descent:
- Splitting the string by ‘=’ to find formal parameters from the left side and storing them along with the right-side expression in a
HashMap
. - Replacing all custom functions in the original expression through
process()
.
Handling Trigonometric Factors
Ele
Each Ele
type now looks like this:
Trigonometric factors inside parentheses are converted to strings as keys, with exponents as values stored in a HashMap
.
Methods for handling trigonometric factors have been added:
Expr
Addition and multiplication require only adding trigonometric factor operations on top of existing logic (addition and multiplication need additional conditions in canMerge()
).
In toString()
, a method triStr(Ele)
for printing trigonometric factors is added.
Program Structure Analysis Based on Metrics
Code Size Analysis
Core code is around 742 lines, with the Expr
class having the most lines at 259, followed by the Parser
class with 150 lines.
Complexity Analysis
Method Complexity
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DeFun.max(int, int) | 0 | 1 | 1 | 1 |
DeFun.process(String) | 22 | 6 | 13 | 13 |
DeFun.readFun(String) | 6 | 3 | 5 | 6 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 5 | 2 | 3 | 6 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 2 | 1 | 3 | 3 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 18 | 5 | 11 | 12 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
Parser.parseTri() | 8 | 1 | 4 | 4 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
expr.Ele.Ele() | 0 | 1 | 1 | 1 |
expr.Ele.canMerge(Ele) | 4 | 1 | 4 | 6 |
expr.Ele.compareTo(Object) | 27 | 13 | 12 | 13 |
expr.Ele.getCoe() | 0 | 1 | 1 | 1 |
expr.Ele.getHashCos() | 0 | 1 | 1 | 1 |
expr.Ele.getHashSin() | 0 | 1 | 1 | 1 |
expr.Ele.getHashVar() | 0 | 1 | 1 | 1 |
expr.Ele.initHashCos(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashSin(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.isOne() | 6 | 1 | 5 | 8 |
expr.Ele.isZero() | 0 | 1 | 1 | 1 |
expr.Ele.putHashCos(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.putHashSin(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashCos(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashSin(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.zero() | 0 | 1 | 1 | 1 |
expr.Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
expr.Expr.getExprList() | 0 | 1 | 1 | 1 |
expr.Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
expr.Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
expr.Expr.merge() | 10 | 4 | 7 | 7 |
expr.Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
expr.Expr.reverse() | 1 | 1 | 2 | 2 |
expr.Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
expr.Expr.simplify() | 4 | 3 | 4 | 4 |
expr.Expr.sort() | 32 | 13 | 12 | 13 |
expr.Expr.toString() | 16 | 1 | 10 | 11 |
expr.Expr.triStr(Ele) | 14 | 1 | 11 | 11 |
expr.Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
expr.Term.getTermList() | 0 | 1 | 1 | 1 |
Total | 233 | 90 | 180 | 195 |
Average | 4.75 | 1.83 | 3.67 | 3.97 |
Class Complexity
UML Class Diagram
The design considerations for each class have been explained in the previous sections on design and architecture, so they will not be repeated here.
Summary of Third Assignment
Design and Architecture
For this assignment, new requirements were introduced:
- Custom function definitions can now call other predefined custom functions.
- A differentiation factor has been added.
Based on the second assignment, the following tasks needed to be completed:
- Improve methods in the
DeFun
class to support calling previously defined custom functions and include differentiation factors. - Add a differentiation method in the
Expr
class.
Improving the Replacement of Custom Functions
DeFun
Replace custom functions that already exist in the HashMap within the same class, then replace the differentiation factors (according to the assignment requirements, simplification and replacement of differentiation factors need to occur at this stage). Therefore, we directly reuse the Parser class to parse the expression inside the differentiation factor and use toString()
to replace the original differentiation factor.
Below is an example snippet:
1 | if (str.contains("d")) { // Differentiation factor appears only once |
Adding Differentiation Methods
Ele
In the Ele
class, add methods to check if it contains a specific variable (‘x’, ‘y’, ‘z’). If it does not contain the variable, the result of differentiation should be zero; otherwise, perform normal differentiation.
Expr
Add support for differentiation methods where derExpr(char)
differentiates with respect to the parameter char
as the independent variable, separately differentiating each Ele
‘s variable part and trigonometric factor part before combining them.
Program Structure Analysis Based on Metrics
Code Size Analysis
Core code is around 899 lines, with the Expr
class having the most lines at 370, followed by the Parser
class with 173 lines.
Complexity Analysis
Method Complexity
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DeFun.findRi(int, String) | 9 | 5 | 6 | 6 |
DeFun.max(int, int) | 0 | 1 | 1 | 1 |
DeFun.process(String) | 10 | 1 | 8 | 8 |
DeFun.readFun(String) | 7 | 3 | 6 | 7 |
Ele.Ele() | 0 | 1 | 1 | 1 |
Ele.canMerge(Ele) | 4 | 1 | 4 | 6 |
Ele.compareTo(Object) | 27 | 13 | 12 | 13 |
Ele.cosContain(String) | 3 | 3 | 2 | 3 |
Ele.getCoe() | 0 | 1 | 1 | 1 |
Ele.getHashCos() | 0 | 1 | 1 | 1 |
Ele.getHashSin() | 0 | 1 | 1 | 1 |
Ele.getHashVar() | 0 | 1 | 1 | 1 |
Ele.initHashCos(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.initHashSin(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.isOne() | 6 | 1 | 5 | 8 |
Ele.isZero() | 0 | 1 | 1 | 1 |
Ele.putHashCos(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
Ele.putHashSin(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashCos(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashSin(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.sinContain(String) | 3 | 3 | 2 | 3 |
Ele.varContain(String) | 1 | 1 | 1 | 2 |
Ele.zero() | 0 | 1 | 1 | 1 |
Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
Expr.derCos(int, String) | 7 | 1 | 5 | 5 |
Expr.derExpr(char) | 14 | 1 | 8 | 8 |
Expr.derSin(int, String) | 7 | 1 | 5 | 5 |
Expr.derVar(int, String) | 0 | 1 | 1 | 1 |
Expr.getExprList() | 0 | 1 | 1 | 1 |
Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
Expr.merge() | 10 | 4 | 7 | 7 |
Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
Expr.reverse() | 1 | 1 | 2 | 2 |
Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
Expr.simplify() | 4 | 3 | 4 | 4 |
Expr.sort() | 32 | 13 | 12 | 13 |
Expr.toString() | 16 | 1 | 10 | 11 |
Expr.triStr(Ele) | 22 | 1 | 13 | 13 |
Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 6 | 2 | 6 | 7 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 2 | 1 | 3 | 3 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseDer(char) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 20 | 6 | 14 | 15 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
Parser.parseTri() | 8 | 1 | 4 | 4 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
Total | 277 | 102 | 214 | 230 |
Average | 4.85 | 1.79 | 3.75 | 4.04 |
Class Complexity
The diagram shows that the Expr
, Ele
, and Parser
classes have higher complexity due to the numerous simplification, calculation, and differentiation methods within the Expr
class.
UML Class Diagram
The design considerations for each class have been explained in the previous sections on design and architecture, so they will not be repeated here.
Bug Analysis
Analyzing Bugs in My Program
First Assignment
- Special cases such as
0^0+5^0+2*10^0
required debugging and modifications intoString()
.
Second Assignment
- When replacing custom functions based on commas, there could be errors when handling nested functions like
f(g(y,z),h(x,y),sin(x))
with more than two commas. The solution was to find and replace the last custom function in each loop (ensuring no nested custom functions are inside). - Pay attention to index positions during replacement, e.g.,
f((((x,y,z))))
might cause bracket judgment errors. - Handle special and edge cases, such as
sin((0))^2
,sin((0))^0
,cos((0))^3
. - When the exponent is 0, directly read the integer rather than parsing the subsequent expression.
Third Assignment
- Simplification after differentiation caused issues, e.g.,
dx(x*cos(x)**0)
would result in incorrect output even though the parsedExpr
was correct. This was because checking if the cosine part containsx
relied on whether theHashMap
was empty (leftover from the second assignment), but I did not remove entries accordingly after differentiation, leading to incorrect output.
Analyzing Bugs in Others’ Programs
Manually construct representative boundary data with multiple levels of nested parentheses and special conditions (results being 0, 1, or no independent variable inside the differentiation), such as:
1 | 1 |
Reflection
The first unit assignments focused on iterative development of expanding expressions and differentiation over three iterations. Initially struggling with understanding the role of Git, I gradually recognized the importance of version control in projects. From feeling lost with IDEA and making frequent mistakes to appreciating its powerful features, and from unfamiliarity with Java to becoming acquainted with its many convenient methods, data structures, and object-oriented thinking. Additionally, the emphasis on coding style has made me increasingly aware of its importance and improved my programming style. The first assignment was challenging due to the lack of knowledge about Java syntax and data structures. Iterative development in the second and third assignments was easier than starting from scratch, but extra care had to be taken to minimize coupling between classes and methods, avoiding changes to existing methods to make bug detection easier. Lastly, reviewing classmates’ design ideas in discussion areas greatly assisted my architectural design. Thank you to the teaching assistants and fellow students for your enthusiastic help!
中文说明
介绍
第一单元作业总的训练目标是对表达式结构进行建模,体会层次化的思想,三次作业为迭代开发。第一次作业是完成对多项式括号的展开,第二次作业新引入了三角函数(sin,cos)以及自定义函数(f(x),g(x,y),h(x,y,z)),第三次作业新增求导因子(dx(表达式))并允许函数定义时调用其他已定义过的函数。
点击此处可以参考我的代码:https://github.com/LeostarR/Object-oriented-Design-and-Construction
第一次作业总结
设计与架构
我的总思路分为三步:
- 预处理表达式
- 解析表达式
- 转化为字符串并打印
预处理表达式
PreStr
预处理:
- 去除所有连续的空白字符(用Java自带的方法
replaceAll
即可解决) - 通过一系列
replaceAll
使得字符串不含有连续的两个及以上的’+’和’-‘(例如”—-“应化为”+”,”+-“应化为”-“) - 将表示幂次的”**”替换为”^”,防止与乘号弄混,产生不必要的bug
解析表达式
Lexer
读取处理:
read()
能够保证每次取出当前的操作数或者运算符next()
能够往后读一个操作数或者运算符- 由于数字长度的不确定,使用
getNumber()
能够取出当前数字
Ele
根据题目要求,我选择新建一个Ele
类完成对基本元素的存储,每一个Ele
应形如:
coe
存储每一个Ele
的系数,hashVar
通过HashMap
存储变量及其对应幂次。
刚开始写Ele
时只写了setCoe()
,getCoe()
,setHashVar()
,getHashVar()
四个方法用于设定值和取值,其他方法是在后续框架的整体搭建中书写的,因此放到后面讲解。
Expr
一连串的表达式形如:
此处系数本身是可能带符号的,因此直接用加号相连。
为了方便数据的调用,我选择ArrayList
来存储多个Ele
:
Expr
方法中,initExperList(ArrayList<Ele>)
,initExpr(Ele)
用于初始化,getExperlist()
用于取出该ArrayList
,其他很多方法是在构建整体的过程中完成的,因此也放到后面。
Parser
使用递归下降解析表达式:
注:dealEx(Expr)
是parseExpr()
的一部分,分离出来是因为方法过长影响码风:)
1.parseExpr()
用于解析表达式,根据定义,表达式由加法和减法运算符连接若干项组成并且在第一项之前,可以带一个正号或者负号,表示第一个项的正负,因此考虑如下结构。
1 | public parseExpr() { |
此处涉及到正负号的处理,因此不在方法中区分加减,而是新建合并的方法Expr.combTerm(ArrayList<Ele> list, String s)
,s
代表符号。若s
为负号,就通过Expr.reverseList(ArrayList<Ele> list)
取反,然后使用addAll()
,考虑性能,我们需要化简,在combTerm
之后使用merge()
合并同类项。
2.parseTerm()
用于解析项,项由乘法运算符连接若干因子组成,总体架构与上文相似,只是要重新写一个乘法的方法,也就是Expr.mulTerm(ArrayList<Ele>)
能够实现两个ArrayList<Ele>
的相乘(实际上就是多个相加)。
3.parseFactor()
用于解析因子。由定义知,因子包括幂函数,常数因子和表达式因子。
1 | public parseFactor() { |
4.parsePow()
同上文分支一样,用于解析幂函数,并返回一个Expr
类。这里单独写是因为方法行数太多影响码风:)
转化为字符串并打印
这一点主要体现在Expr
类中的toString()
方法。总的思路是设置StringBuilder
然后不断append
。对ArrayList
中的每一个Ele
依次判断是否有’+’,是否为正负1,幂次是否为0,整体是否为0。(这一部分细节还挺多的需要特别注意)
基于度量的程序结构分析
代码规模分析
核心代码在450行左右,Expr
类行数最多,占到了175行,其次是119行的解析Parser
类。
复杂度分析
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 3 | 2 | 3 | 4 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 16 | 4 | 9 | 10 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
expr.Ele.Ele() | 0 | 1 | 1 | 1 |
expr.Ele.canMerge(Ele) | 2 | 1 | 3 | 4 |
expr.Ele.compareTo(Object) | 7 | 5 | 5 | 5 |
expr.Ele.getCoe() | 0 | 1 | 1 | 1 |
expr.Ele.getHashVar() | 0 | 1 | 1 | 1 |
expr.Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.isOne() | 4 | 1 | 4 | 6 |
expr.Ele.isZero() | 0 | 1 | 1 | 1 |
expr.Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.zero() | 0 | 1 | 1 | 1 |
expr.Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
expr.Expr.getExprList() | 0 | 1 | 1 | 1 |
expr.Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
expr.Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
expr.Expr.merge() | 10 | 4 | 7 | 7 |
expr.Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
expr.Expr.reverse() | 1 | 1 | 2 | 2 |
expr.Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
expr.Expr.sort() | 9 | 5 | 5 | 5 |
expr.Expr.toString() | 16 | 1 | 10 | 11 |
expr.Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
Total | 118 | 49 | 109 | 117 |
Average | 3.47 | 1.44 | 3.21 | 3.44 |
类复杂度
表中可以看出最复杂的三个方法:Parser.parseFactor()
,expr.Expr.toString()
和expr.Expr.varStr(Ele)
。由于因子的可能类型较多,讨论情况较多,因此复杂度也较高。后两者都是转化为字符串的方法,由于情况种类较多,因此复杂度上升较显著。
图中也能看到Expr
类,Ele
类和Parser
类的复杂度较高,Parser
类中分解的方法较多,Expr
和Ele
中计算合并之类的方法较多导致了这几个类的复杂度较高。
附:度量分析条目解释
- CogC:认知复杂度
- ev(G):非抽象方法的基本复杂度,用以衡量一个方法的控制流结构缺陷,范围是 [1, v(G)]
- iv(G):方法的设计复杂度,用以衡量方法控制流与其他方法之间的耦合程度,范围是 [1, v(G)]
- v(G):非抽象方法的圈复杂度,用以衡量每个方法中不同执行路径的数量
- OC:类的非抽象方法圈复杂度,继承类不计入
- WMC:类的总圈复杂度
UML类图
每个类的设计考虑已在上文的设计与架构中说明,此处不再赘述。
第二次作业总结
设计与架构
本次作业新增了这些要求:
- 支持嵌套多层括号
- 新增三角函数因子,三角函数括号内部包含任意因子
- 新增自定义函数因子,但自定义函数的函数表达式中不会调用其他函数
因此在第一次作业的基础上,需要完成这些工作:
- 替换自定义函数
- 引入新的数据结构能够存储三角因子
- 增加或完善方法并完成三角函数的计算与化简
替换自定义函数
DeFun
仅仅实现替换,替换之后即可按照之前的递归下降实现表达式解析:
- 根据’=’分割成左右两字符串,左边的可根据’,’找出形参,将形参和右边的表达式存入
HashMap
- 在原表达式中通过
process()
替换所有的自定义函数
处理三角因子
Ele
本次作业每个Ele
类型应形如:
将三角因子括号内部的因子toString
作为key
,指数作为value
再存入HashMap
:
增加了处理三角因子的方法:
Expr
加法和乘法只需在原有基础上添加三角因子的运算即可(加法乘法需要在canMerge()
中添加条件)。
在toString()
中加上打印三角因子的方法triStr(Ele)
。
基于度量的程序结构分析
代码规模分析
核心代码在742行左右,Expr
类行数最多,占到了259行,其次是150行的解析Parser
类。
复杂度分析
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DeFun.max(int, int) | 0 | 1 | 1 | 1 |
DeFun.process(String) | 22 | 6 | 13 | 13 |
DeFun.readFun(String) | 6 | 3 | 5 | 6 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 5 | 2 | 3 | 6 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 2 | 1 | 3 | 3 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 18 | 5 | 11 | 12 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
Parser.parseTri() | 8 | 1 | 4 | 4 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
expr.Ele.Ele() | 0 | 1 | 1 | 1 |
expr.Ele.canMerge(Ele) | 4 | 1 | 4 | 6 |
expr.Ele.compareTo(Object) | 27 | 13 | 12 | 13 |
expr.Ele.getCoe() | 0 | 1 | 1 | 1 |
expr.Ele.getHashCos() | 0 | 1 | 1 | 1 |
expr.Ele.getHashSin() | 0 | 1 | 1 | 1 |
expr.Ele.getHashVar() | 0 | 1 | 1 | 1 |
expr.Ele.initHashCos(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashSin(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
expr.Ele.isOne() | 6 | 1 | 5 | 8 |
expr.Ele.isZero() | 0 | 1 | 1 | 1 |
expr.Ele.putHashCos(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.putHashSin(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
expr.Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashCos(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashSin(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
expr.Ele.zero() | 0 | 1 | 1 | 1 |
expr.Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
expr.Expr.getExprList() | 0 | 1 | 1 | 1 |
expr.Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
expr.Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
expr.Expr.merge() | 10 | 4 | 7 | 7 |
expr.Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
expr.Expr.reverse() | 1 | 1 | 2 | 2 |
expr.Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
expr.Expr.simplify() | 4 | 3 | 4 | 4 |
expr.Expr.sort() | 32 | 13 | 12 | 13 |
expr.Expr.toString() | 16 | 1 | 10 | 11 |
expr.Expr.triStr(Ele) | 14 | 1 | 11 | 11 |
expr.Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
expr.Term.getTermList() | 0 | 1 | 1 | 1 |
Total | 233 | 90 | 180 | 195 |
Average | 4.75 | 1.83 | 3.67 | 3.97 |
类复杂度
UML类图
每个类的设计考虑已在上文的设计与架构中说明,此处不再赘述。
第三次作业总结
设计与架构
本次作业新增了这些要求:
- 自定义函数定义时可以调用已经定义过的自定义函数
- 新增求导因子
因此,在第二次作业的基础上,需要完成这些工作:
- 在
DeFun
类中完善方法使支持调用定义过的自定义函数并且包含求导因子 - 在
Expr
类中新增求导的方法
完善替换自定义函数的方法
DeFun
在同一个类中替换已经存在于HashMap中的自定义函数,然后替换求导因子(根据题目要求,需要在这里将求导因子化简并替换),因此我们直接复用之前的Parser类解析求导因子内部的表达式,并且用toString()
替换原求导因子。
下面是可能的示例:
1 | if (str.contains("d")) { //求导因子只出现一次 |
增加求导的方法
Ele
在Ele
中增加方法支持判断该Ele
是否包含该变量(’x’,’y’,’z’),若不包含,则求导的结果为零,反之则可进行正常的求导。
Expr
增加支持求导的方法,derExpr(char)
中对参数char
作为自变量求导,分别对每一个Ele
的变量部分,三角因子部分进行求导再合并。
基于度量的程序结构分析
代码规模分析
核心代码在899行左右,Expr
类行数最多,占到了370行,其次是173行的解析Expr
类。
复杂度分析
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DeFun.findRi(int, String) | 9 | 5 | 6 | 6 |
DeFun.max(int, int) | 0 | 1 | 1 | 1 |
DeFun.process(String) | 10 | 1 | 8 | 8 |
DeFun.readFun(String) | 7 | 3 | 6 | 7 |
Ele.Ele() | 0 | 1 | 1 | 1 |
Ele.canMerge(Ele) | 4 | 1 | 4 | 6 |
Ele.compareTo(Object) | 27 | 13 | 12 | 13 |
Ele.cosContain(String) | 3 | 3 | 2 | 3 |
Ele.getCoe() | 0 | 1 | 1 | 1 |
Ele.getHashCos() | 0 | 1 | 1 | 1 |
Ele.getHashSin() | 0 | 1 | 1 | 1 |
Ele.getHashVar() | 0 | 1 | 1 | 1 |
Ele.initHashCos(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.initHashSin(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.initHashVar(HashMap<String, BigInteger>) | 0 | 1 | 1 | 1 |
Ele.isOne() | 6 | 1 | 5 | 8 |
Ele.isZero() | 0 | 1 | 1 | 1 |
Ele.putHashCos(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
Ele.putHashSin(HashMap<String, BigInteger>) | 4 | 1 | 3 | 3 |
Ele.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashCos(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashSin(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.setHashVar(String, BigInteger) | 0 | 1 | 1 | 1 |
Ele.sinContain(String) | 3 | 3 | 2 | 3 |
Ele.varContain(String) | 1 | 1 | 1 | 2 |
Ele.zero() | 0 | 1 | 1 | 1 |
Expr.combTerm(ArrayList, String) | 1 | 1 | 2 | 2 |
Expr.derCos(int, String) | 7 | 1 | 5 | 5 |
Expr.derExpr(char) | 14 | 1 | 8 | 8 |
Expr.derSin(int, String) | 7 | 1 | 5 | 5 |
Expr.derVar(int, String) | 0 | 1 | 1 | 1 |
Expr.getExprList() | 0 | 1 | 1 | 1 |
Expr.initExpr(Ele) | 0 | 1 | 1 | 1 |
Expr.initExprList(ArrayList) | 0 | 1 | 1 | 1 |
Expr.merge() | 10 | 4 | 7 | 7 |
Expr.mulTerm(ArrayList) | 3 | 1 | 3 | 3 |
Expr.reverse() | 1 | 1 | 2 | 2 |
Expr.reverseList(ArrayList) | 1 | 1 | 2 | 2 |
Expr.simplify() | 4 | 3 | 4 | 4 |
Expr.sort() | 32 | 13 | 12 | 13 |
Expr.toString() | 16 | 1 | 10 | 11 |
Expr.triStr(Ele) | 22 | 1 | 13 | 13 |
Expr.varStr(Ele) | 21 | 1 | 16 | 16 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 6 | 2 | 6 | 7 |
Lexer.read() | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 2 | 1 | 3 | 3 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.dealEx(Expr) | 0 | 1 | 1 | 1 |
Parser.parseDer(char) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 5 | 5 |
Parser.parseFactor() | 20 | 6 | 14 | 15 |
Parser.parsePow() | 2 | 1 | 2 | 2 |
Parser.parseTerm() | 9 | 1 | 6 | 7 |
Parser.parseTri() | 8 | 1 | 4 | 4 |
PreStr.pre(String) | 6 | 1 | 6 | 7 |
Total | 277 | 102 | 214 | 230 |
Average | 4.85 | 1.79 | 3.75 | 4.04 |
类复杂度
图中也能看到Expr
类,Ele
类和Parser
类的复杂度较高,Expr类中大量的化简、计算和求导方法使得该类复杂度很高。
UML类图
每个类的设计考虑已在上文的设计与架构中说明,此处不再赘述。
bug分析
分析自己程序的bug
第一次作业
- 一些特殊点的情况,例如:0^0+5^0+2*10^0,需要提前做好调试并在
toString()
中修改
第二次作业
- 替换自定义函数时根据逗号分割,因此在面对嵌套函数例如f(g(y,z),h(x,y),sin(x))中存在多于2个逗号的情况可能会出错,解决方案是每次循环找到最靠后的一个自定义函数(保证这个函数里异地没有嵌套的自定义)进行替换
- 注意替换时索引的位置,例如f((((x,y,z))))可能会出现括号判断错误
- 注意特殊和边界点,例如sin((0))^2,sin((0))^0,cos((0))^3
- 幂次为0时应该直接读取整数而不是往后解析表达式(大概是脑子抽了才这样写,大部分类似的问题全都是这点引起的)
第三次作业
- 求导之后化简会出问题,例如dx(x*cos(x)**0)最后解析得到的Expr没有问题但是输出会出错,原因在于我判断cos部分含不含x是根据HashMap是否为空(第二次作业的遗留),但是在求导之后我没有进行相应的remove工作,导致输出错误
分析他人程序的bug
手动构造具有代表性的边界数据,拥有多层嵌套括号,同时包含特殊条件(结果为0,1,求导内部无自变量),例如:
1 | 1 |
心得体会
第一单元作业主要是针对表达式括号展开和求导的三次迭代开发。从最开始的不理解git的作用,到现在渐渐认识到版本管理在工程中的重要性;从对IDEA的无所适从和屡次犯错,到逐渐领略到这款IDE功能的强大;从最开始的对Java感到陌生,到逐渐熟悉它的众多优良简便的方法、数据结构和其面向对象的思维。同时,三次作业对代码风格的要求使我进一步意识到码风的重要性并逐渐内在地提升了自己编程的码风。第一次作业由于只有整体的架构,在缺少对Java语法以及数据结构等知识的缺乏的情况下,完成得非常艰难。在第二次第三次的迭代开发中,任务量明显比第一次的从零开始要轻松一些,但是要特别特别注意类以及方法之间的耦合性,尽量不改变现有的方法,这样更容易发现代码的bug。最后,我认为可以多参考讨论区里同学们的设计思路,这对于我的架构设计有很大的帮助^_^ 感谢助教和同学们的热心帮助!
- Title: Summary of Object-Oriented Design and Construction Unit 1
- Author: Leostar
- Created at : 2023-03-17 20:56:47
- Updated at : 2024-12-18 15:08:05
- Link: https://leostarr.github.io/2023/03/17/1Summary of Object-Oriented Design and Construction Unit 1/
- License: This work is licensed under CC BY-NC-SA 4.0.