軟工作業3:結對編程【四則運算】
學生個人信息
| 姓名 | 學號 |
|---|---|
| 鄭耿杭 | 3121004978 |
| 梁鴻俊 | 3121004956 |
作業基本信息
| 這個作業屬于哪個課程 | 軟件工程 |
|---|---|
| 作業要求 | 結對編程 |
| 作業目標 | 實現一個自動生成小學四則運算的程序 |
Github
https://github.com/lianghongjun/operation
PSP表格
| PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
|---|---|---|---|
| Planning | 計劃 | 30 | 20 |
| Estimate | 估計這個任務需要多少時間 | 30 | 15 |
| Development | 開發 | 180 | 200 |
| Analysis | 需求分析 | 120 | 90 |
| Design Spec | 生成設計文檔 | 10 | 10 |
| Design Review | 設計復審 | 10 | 10 |
| Coding Standard | 代碼規范 | 5 | 5 |
| Design | 具體設計 | 50 | 60 |
| Coding | 具體編碼 | 80 | 80 |
| Code Review | 代碼復審 | 30 | 20 |
| Test | 測試 | 40 | 50 |
| Reporting | 報告 | 10 | 10 |
| Test Report | 測試報告 | 10 | 10 |
| Size Measurement | 計算工作量 | 20 | 20 |
| Postmortem & Process Improvement Plan | 事后總結, 并提出過程改進計劃 | 10 | 10 |
| 合計 | 625 | 610 |
設計實現過程
項目目錄

1.Arithmetic:算術類(子類:運算符類Operator,操作數類Operand,括號類Bracket)
2.Brackets:括號類
3.Equation:表達式類
4.Operand:操作數類
5。Operator:運算符類
6.FileUtils:文件工具類,包括將內容寫入指定路徑文件和從指定路徑文件讀取成表達式數組的方法
代碼說明
1.關鍵代碼
filter()
說明:用于過濾重復的表達式
思路:
1.按順序層層篩選,由于轉換成后綴表達式,不用考慮括號
2.先去除運算過程含負數的
3.先比較結果
4.比較表達式是否一樣
5.再比較包含的運算符是否相同
6.比較第一次運算的兩數是否只是交換位置
點擊查看代碼
/**
* 用來過濾重復表達式,按順序層層篩選,由于轉換成后綴表達式,不用考慮括號
* 1. 先去除運算過程含負數的
* 2. 先比較結果
* 3. 比較表達式是否一樣
* 4. 再比較包含的運算符是否相同
* 5. 比較第一次運算的兩數是否只是交換位置
*
* @param list 要過濾的表達式數組
* @return 過濾完成的表達式
*/
public static List<Equation> filter(List<Equation> list) {
for (int i = 0; i < list.size(); i++) {
Equation equation = list.get(i);
// 如果運算過程含負數,則跳過
if (equation.isOf()) {
list.remove(equation);
//remove會整體前移
i--;
continue;
}
// 和整個list比較
// 標簽方便下面層層嵌套能直接goto出來
flag:
for (int o = 0; o < list.size(); o++) {
Equation toCompare = list.get(o);
// 刪除后有空位,要跳過
if (toCompare == null) {
continue;
}
// 遇到自己就跳過
if (equation == toCompare) {
continue;
}
// 先比較結果
if (Math.abs(equation.getResult() - toCompare.getResult()) < 0.000001) {
// 結果相同,看是否完全一樣
if (equation.equals(toCompare)) {
list.remove(equation);
// remove會整體前移
i--;
break flag;
}
// 再比較運算符
List<Arithmetic> postfix1 = equation.getPostfix();
List<Arithmetic> postfix2 = toCompare.getPostfix();
List<Operator> operators1 = equation.getOperators();
List<Operator> operators2 = toCompare.getOperators();
// 有不同運算符就保留
if (operators1.size() != operators2.size()) {
break flag;
}
for (int j = 0; j < operators1.size(); j++) {
if (operators1.get(j) != operators2.get(j)) {
break flag;
}
}
// 運算符相同,只比較第一次計算的兩數字是否交換位置
// 找到第一個運算符,取前兩個數字
List<Operand> operands1 = new ArrayList<>();
List<Operand> operands2 = new ArrayList<>();
for (int j = 0; j < postfix1.size(); j++) {
if (postfix1.get(j) instanceof Operator) {
operands1.add((Operand) postfix1.get(j - 1));
operands1.add((Operand) postfix1.get(j - 2));
break;
}
}
for (int j = 0; j < postfix1.size(); j++) {
if (postfix2.get(j) instanceof Operator) {
operands2.add((Operand) postfix2.get(j - 1));
operands2.add((Operand) postfix2.get(j - 2));
break;
}
}
// 比較兩對數字
if ((operands1.get(0).equals(operands2.get(0)) || operands1.get(0).equals(operands2.get(1))) &&
(operands1.get(1).equals(operands2.get(0)) || operands1.get(1).equals(operands2.get(1)))) {
list.remove(equation);
//remove會整體前移
i--;
}
// 兩對數字不相同,保留
break;
} else {
//結果不一樣,保留
break;
}
}
}
return list.stream().toList();
}
generate()
說明:用于生成隨機表達式
思路:通過傳參確定此次生成中包含的操作數數量、運算符數量、括號數量、數的范圍,然后隨機new出各對象,交替拼接操作數和運算符,最后隨機添加括號
點擊查看代碼
/**
* 用來生成隨機表達式
*
* @param operandNo 操作數數量
* @param operatorNo 運算符數量
* @param bracketsNo 括號數量
* @param lowEnd 生成表達式中操作數和真分數分母范圍的下限
* @param upEnd 生成表達式中操作數和真分數分母范圍的上限
* @return 生成的表達式
*/
public static Equation generate(int operandNo, int operatorNo, int bracketsNo
, int lowEnd, int upEnd) {
Random r = new Random();
int scope = upEnd - lowEnd;
List<Arithmetic> arithmetics = new ArrayList<>();
List<Operand> operands = new ArrayList<>();
List<Operator> operators = new ArrayList<>();
List<Brackets> brackets = new ArrayList<>();
try {
for (int i = 0; i < operandNo; i++) {
// 操作數類型 自然數(0),真分數(1)
int type = r.nextInt(10) % 2;
if (0 == type) {
// 生成隨機整數
operands.add(new Operand(type, r.nextInt(scope) + lowEnd + ""));
} else {
// 生成真分數
int denominator = r.nextInt(scope) + lowEnd + 1;
// 分子 > 0
int numerator = r.nextInt(denominator - 1) + 1;
String str = numerator + "/" + denominator;
operands.add(new Operand(type, str));
}
}
for (int i = 0; i < operatorNo; i++) {
// 除去等號
int index = r.nextInt(4) + 1;
operators.add(Operator.getByIndex(index));
}
for (int i = 0; i < bracketsNo; i++) {
brackets.add(Brackets.getByIndex(0));
brackets.add(Brackets.getByIndex(1));
}
for (int i = 0; i < operands.size(); i++) {
if (operands.get(i) != null) {
arithmetics.add(operands.get(i));
}
if (i == operands.size() - 1) {
break;
}
if (operators.get(i) != null) {
arithmetics.add(operators.get(i));
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return new Equation(arithmetics);
}
infixToPostfix()
說明: 將中綴表達式轉換成后綴表達式
思路:
無括號:
1.掃描中綴表達式的每一個字符,將數字入列;
2.遇到運算符,??諘r直接進棧,棧頂非空時,運算符優先級大于棧頂元素才進棧,
否則棧頂元素退棧入列,當前運算符再進棧;
3.依次進行直至所有字符操作完畢
有括號:
1.掃描中綴表達式的每一個字符,將數字入列;
2.遇到運算符,??諘r直接進棧,棧頂非空時,運算符優先級大于棧頂元素才進棧,
否則棧頂元素退棧入列,當前運算符再進棧;
3.遇到左括號,直接進棧,左括號后面的運算符直接進棧,直至遇到右括號;
4.遇到右括號時,將棧頂元素依次退棧入列,直到遇到左括號,將左括號退棧,符號操作移動下一位
5.重復以上操作,直至所有字符操作完成。
點擊查看代碼
/**
* 將中綴表達式轉換為后綴表達式
*
* @return 返回轉換結果
*/
public List<Arithmetic> infixToPostfix() {
Stack<Arithmetic> stack = new Stack<>();
List<Arithmetic> postfix = new ArrayList<>();
for (int start = 0; start < infix.size(); start++) {
// 如果是運算符
if (infix.get(start).priority > 0) {
// 棧空 或 "(" 或 符號優先級>棧頂符號 且 不為")" 直接進棧
if (stack.isEmpty() || infix.get(start).priority == 3 ||
((infix.get(start).priority > stack.peek().priority) && infix.get(start).priority < 4)) {
stack.push(infix.get(start));
} else if (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
// 棧非空 且 符號優先級≤棧頂符號, 出棧; 直到 棧為空 或 遇到了"("
while (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
if (stack.peek().priority == 3) {
stack.pop();
break;
}
postfix.add(stack.pop());
}
stack.push(infix.get(start));
} else if (infix.get(start).priority == 4) {
// ")",依次出棧直到空?;蛴龅降谝粋€"(",此時"("出棧
while (!stack.isEmpty()) {
if (stack.peek().priority == 3) {
stack.pop();
break;
}
postfix.add(stack.pop());
}
}
} else if (infix.get(start).priority == -1) {
postfix.add(infix.get(start));
}
}
while (!stack.isEmpty()) {
postfix.add(stack.pop());
}
return postfix;
}
測試結果
代碼隨機生成10000條計算式

代碼隨機生成10000條計算式結果

代碼隨機生成計算式

計算式計算結果

計算結果統計

效能分析


消耗最大的函數
用于去除重復的函數:Equation.filter()
因為里面用了許多邏輯判斷和嵌套循環,時間復雜度高,隨生成數量提高而呈冪次增長
浙公網安備 33010602011771號