成員:前端
巫培傑:3117004669git
易俊楷:3117004677github
1、Github項目地址ajax
https://github.com/blanche789/softpairing編程
2、PSP表格後端
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 30 |
· Estimate | · 估計這個任務須要多少時間 | 30 | 30 |
Development | 開發 | 1525 | 1650 |
· Analysis | · 需求分析 | 90 | 85 |
· Design Spec | · 生成設計文檔 | 60 | 50 |
· Design Review | · 設計複審 | 35 | 45 |
· Coding Standard | · 代碼規範 | 90 | 80 |
· Design | · 具體設計 | 60 | 60 |
· Coding | · 具體編碼 | 1000 | 1100 |
· Code Review | · 代碼複審 | 100 | 120 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 90 | 110 |
Reporting | 報告 | 160 | 150 |
· Test Report | · 測試報告 | 60 | 50 |
· Size Measurement | · 計算工做量 | 40 | 30 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 60 | 70 |
Total | 總計 | 1715 | 1830 |
3、效能分析服務器
以10000題目爲初始數量,來進行壓力測試,共耗時0.76s數據結構
因爲生成題目過程當中我利用了一層for循環,時間複雜度爲O(1),因此生成效率比較高架構
4、設計實現過程app
本項目是先後端分離的項目,我是負責後端的技術,後端採用的技術棧是Spring boot,採用了當下流行的微服務架構,簡化了開發流程。我後端與前端的交互是經過接口進行的,實現了徹底的解耦,作到了真正的分工合做。分爲三個包,六個類(除去一個啓動類),以下流程圖:
其中ExerciseController類是提供接口的類,內部包含了三個函數,分別是index、generate、download,index函數爲用戶首次跳轉到交互界面提供接口,generate函數提供了生成題目和答案的接口,download函數提供了下載習題、答案的接口。其中以Generate函數做爲主函數來展開講解,此函數經過用戶所須要的題目數量,來進行迭代生成題目,其中經過隨機數來決定每一道題目的類型,題目分紅兩種類型(含分數的四則運算、整數的四則運算)。
Generate類:
GenerateInterger是生成整數題目的函數,會先隨機生成操做符和操做數,而後對其進行相應的計算。而後分別將題目和答案封裝進Exercise
GenerateFraction是生成分數題目的函數,先生成操做符以及第一個分數,而後根據操做符的數量進行迭代,每迭代一次,生成下一個操做數,而後判斷是否符合相減爲正數,若正常則生成相應的分數,並進行計算,將計算結果轉換爲假分數,而後將題目和答案封裝進Exercise
Auxiliary類:
此類爲輔助類,將Generate類中函數所須要的一些操做封裝成函數寫進此類,一是能夠規範代碼,提升代碼的可讀性;二是對解耦代碼,清晰開發流程;
該類中有commonDivison(生成最大公約數)、indexArray(生成運算符匹配的索引)、splicingFormula(拼接式子與操做符)、primeNumber(生成互質的分數)、properFranction(轉換爲真分數)、caculate(計算兩個整數)
Caculator類:
此類爲計算整數結果的類,由於整數的四則運算涉及到括號與運算符優先的機制,因此我利用了棧的思想來進行計算,利用了符號棧和操做數棧
5、代碼說明
1.後端代碼加載題目入口:
@RequestMapping(value = "/generate" , method = RequestMethod.POST)
@ResponseBody()
public List<Exercise> generate(@RequestBody Command command) {//command接收前端傳遞的指令
generate = new Generate();
int exerciseNum = command.getQuestionNum();
int range = command.getNumericalRange();
list = new ArrayList<>();
Random random = new Random();
int choose;
for (int i = 0; i < exerciseNum; i++) { //根據題目數量進行迭代
choose = random.nextInt(2);
if (choose == 1) {
list.add(generate.generateInterger(i,range));
} else {
list.add(generate.generateFraction(i,range));
}
}
return list;//將全部的題目封裝到list裏,響應給前端的ajax
}
2.生成題目,並生成答案的代碼
public Exercise generateInterger(int qid, int numRange) {//生成整數的題目 Random random = new Random(); Exercise exercise = new Exercise(); int operatorNum = random.nextInt(3) + 1; //隨機生成操做符數量 int[] operatorIndex = auxiliary.indexArray(operatorNum , 4); //根據操做符數量生成相應的隨機索引 int[] operands = new int[operatorNum + 1]; for (int i = 0; i < operands.length; i++) { //生成操做數 operands[i] = random.nextInt(numRange); } String formula = auxiliary.splicingFormula(operatorIndex, operands, operatorNum); //拼接符號和題目 //計算出答案 int answer = calculator.caculate(formula); if (answer > 0) { String answerStr = String.valueOf(answer); exercise.setQid(qid); exercise.setExercise(formula); exercise.setAnswer(answerStr); } else { //若生成的答案<0,則遞歸從新生成題目 return generateInterger(qid, numRange); } return exercise; } public Exercise generateFraction(int qid,int numRange) {//生成含分數的題目 Random random = new Random(); Exercise exercise = new Exercise(); int operatorNum = random.nextInt(2) + 1; int[] operatorIndex = auxiliary.indexArray(operatorNum , 2); //生成一個分數 int[] fraction = auxiliary.primeNumber(numRange); int x = fraction[0]; int y = fraction[1]; String properFraction = auxiliary.properFraction(x, y); String s = properFraction; for (int i = 0; i < operatorNum; i++) { //生成下個個分數 int[] nextFraction = auxiliary.primeNumber(numRange); int nextx = nextFraction[0]; //分子 int nexty = nextFraction[1]; //分母 if (operatorArr[operatorIndex[i]].equals("+")) { x = x * nexty + nextx * y; y = y * nexty; }else { int count = 0; while (x * nexty - nextx * y < 0) { count++; nextFraction = auxiliary.primeNumber(numRange); nextx = nextFraction[0]; nexty = nextFraction[1]; if (count == 5) { //若出現了五次生成的題目相減爲負數則利用上個數來進行生成下一個書 nextx = x - 1; nexty = y; } } x = x * nexty - nextx * y; y = y * nexty; } String nextProperFraction = auxiliary.properFraction(nextx, nexty); s += operatorArr[operatorIndex[i]] + nextProperFraction; } int divisor = auxiliary.commonDivisor(x, y); if (divisor != 1) { x /= divisor; y /= divisor; } String finalProperFraction = auxiliary.properFraction(x, y); s += "="; exercise.setQid(qid); exercise.setExercise(s); exercise.setAnswer(finalProperFraction); return exercise; }
3.生成整數答案時的棧操做
public int caculate(String formula) { Auxiliary auxiliary = new Auxiliary(); Map<String, Integer> map = new HashMap<>(); //設置操做符優先級 map.put("(", 0); map.put("+", 1); map.put("-", 1); map.put("*", 2); map.put("÷", 2); //存放數字的棧 Stack<Integer> integerStack = new Stack<>(); //存放符號的棧 Stack<String> operatorStack = new Stack<>(); String trueFormula = formula.replaceAll(" ", ""); for (int i = 0; i < trueFormula.length();) { StringBuilder stringBuilder = new StringBuilder(); char c = trueFormula.charAt(i); while (Character.isDigit(c)) { stringBuilder.append(c); i++; if (i < trueFormula.length()) { c = trueFormula.charAt(i); }else { break; } } if (stringBuilder.length() == 0) {//代表當前所取的爲符號 String operator; switch (c) { case '(': operatorStack.push(String.valueOf(c)); break; case ')': //右括號優先級最高,需進行計算 operator = operatorStack.pop(); while (!operatorStack.isEmpty() && !operator.equals("(")) { int a = integerStack.pop(); int b = integerStack.pop(); int result = auxiliary.caculate(b, a, operator); if (result < 0) { return -1; } integerStack.push(result); operator = operatorStack.pop(); } break; case '=': while (!operatorStack.isEmpty()) {//若操做符棧不爲空 while (!operatorStack.isEmpty()) { operator = operatorStack.pop(); int a = integerStack.pop(); int b = integerStack.pop(); int result = auxiliary.caculate(b, a, operator); if (result < 0) { return -1; } integerStack.push(result); } } break; default: while (!operatorStack.isEmpty()) { //將操做符入棧 operator = operatorStack.pop(); if (map.get(operator) >= map.get(String.valueOf(c))) {//與上個操做符進行優先級比較,由於優先級的運算符是做用於優先於其前一個字符的,因此若優先級高則先計算該運算符 int a = integerStack.pop(); int b = integerStack.pop(); int result = auxiliary.caculate(b, a, operator); if (result < 0) { return -1; } integerStack.push(result); } else { operatorStack.push(operator); break; } } operatorStack.push(String.valueOf(c)); break; } } else { integerStack.push(Integer.valueOf(stringBuilder.toString())); continue; } i++; } return integerStack.peek(); }
6、測試運行
1.輸入指令
2.生成題目界面預覽
3.生成答案界面預覽
三、下載答案預覽
4.答案校驗
5.下載題目成功
6.下載答案成功
7、項目小結
這次項目學會先後端分離應該能夠說是我最大的收穫。在這個過程當中,咱們是先分析好大體的需求,前端如何根據我接口獲取到我後臺的數據,而後面向接口編程。因爲未租借服務器,因此每次前端的小夥伴寫完頁面的時候都須要把頁面拿到個人本機來運行,這個過程算是比較繁瑣的,由於中間不定時會出現bug,因此兩我的有時也會一塊兒討論交接在哪裏出問題了。因爲這個項目是和我舍友一塊兒進行的,因此在討論起來是會比較方便,同時也體會到了團隊合做中和諧的重要性,只有耐心的聽取他人的意見,纔可能最優化的解決問題,這於雙反而言都是最有的作法,換位思考在團隊合做中也是我學到的一個很重要的思想。因爲先後端的實現內容不同,因此偶爾在後臺開發遇到難題時,也會跟同伴吐槽一下,他也會給我出謀劃策,但總歸而言,因爲項目的分離,因此咱們仍是分工比較明確的,在各自的工做方面也沒法給與太多的意見。
關於項目自己,本次我以爲最難處理同時也是收穫比較大的是棧處理,根據符號的優先級,以及括號的優先級,將數據結構的思想運用到具體的項目中,是我比較大的一個收穫。這個難題也是糾結了我好久,而且有參考網上關於棧的操做,最後處理出來,將各類邊界條件考慮清楚,從而實現基本功能