member:
Wu Peijie: 3117004669
Yi Chun Kai: 3117004677
A, Github project address
https://github.com/blanche789/softpairing
Two, PSP form
PSP2.1 | Personal Software Process Stages | Estimated time consuming (minutes) | The actual time-consuming (minutes) |
---|---|---|---|
Planning | plan | 30 | 30 |
· Estimate | • Estimate how much time this task requires | 30 | 30 |
Development | Develop | 1525 | 1650 |
· Analysis | · demand analysis | 90 | 85 |
· Design Spec | Generate design documents | 60 | 50 |
· Design Review | · Design Review | 35 | 45 |
· Coding Standard | · Code Specification | 90 | 80 |
· Design | · Specific design | 60 | 60 |
· Coding | · Specific coding | 1000 | 1100 |
· Code Review | · Code Review | 100 | 120 |
· Test | · Test (self-test, modify the code, submit modifications) | 90 | 110 |
Reporting | report | 160 | 150 |
· Test Report | · testing report | 60 | 50 |
· Size Measurement | · Computing workload | 40 | 30 |
· Postmortem & Process Improvement Plan | · Hindsight, and propose process improvement plan | 60 | 70 |
Total | total | 1715 | 1830 |
Third, performance analysis
The initial number 10000 entitled to carry out stress tests, took a total 0.76s
Since a task is generated during the use of a layer I for loop, the time complexity is O (1), thus generating a more efficient
Fourth, the design and implementation process
This project is separate from the front and rear end of the project, I was responsible for the back-end technology, back-end technology stack is used in Spring boot, using the current popular micro-services architecture simplifies the development process. I interact with the rear end of the front end is carried out through the interface, to achieve a complete decoupling, so that the real division of labor. Package is divided into three, six classes (class removing a start), the following flow chart:
其中ExerciseController类是提供接口的类,内部包含了三个函数,分别是index、generate、download,index函数为用户首次跳转到交互界面提供接口,generate函数提供了生成题目和答案的接口,download函数提供了下载习题、答案的接口。其中以Generate函数作为主函数来展开讲解,此函数通过用户所需要的题目数量,来进行迭代生成题目,其中通过随机数来决定每一道题目的类型,题目分成两种类型(含分数的四则运算、整数的四则运算)。
Generate类:
GenerateInterger是生成整数题目的函数,会先随机生成操作符和操作数,然后对其进行相应的计算。然后分别将题目和答案封装进Exercise
GenerateFraction是生成分数题目的函数,先生成操作符以及第一个分数,然后根据操作符的数量进行迭代,每迭代一次,生成下一个操作数,然后判断是否符合相减为正数,若正常则生成相应的分数,并进行计算,将计算结果转换为假分数,然后将题目和答案封装进Exercise
Auxiliary类:
此类为辅助类,将Generate类中函数所需要的一些操作封装成函数写进此类,一是可以规范代码,提高代码的可读性;二是对解耦代码,清晰开发流程;
该类中有commonDivison(生成最大公约数)、indexArray(生成运算符匹配的索引)、splicingFormula(拼接式子与操作符)、primeNumber(生成互质的分数)、properFranction(转换为真分数)、caculate(计算两个整数)
Caculator类:
此类为计算整数结果的类,因为整数的四则运算涉及到括号与运算符优先的机制,所以我利用了栈的思想来进行计算,利用了符号栈和操作数栈
五、代码说明
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(); }
六、测试运行
1.输入指令
2.生成题目界面预览
3.生成答案界面预览
3、下载答案预览
4.答案校验
5.下载题目成功
6.下载答案成功
七、项目小结
此次项目学会前后端分离应该可以说是我最大的收获。在这个过程中,我们是先分析好大致的需求,前端如何根据我接口获取到我后台的数据,然后面向接口编程。由于未租借服务器,所以每次前端的小伙伴写完页面的时候都需要把页面拿到我的本机来运行,这个过程算是比较繁琐的,因为中间不定时会出现bug,所以两个人有时也会一起讨论交接在哪里出问题了。由于这个项目是和我舍友一起进行的,所以在讨论起来是会比较方便,同时也体会到了团队合作中和谐的重要性,只有耐心的听取他人的意见,才可能最优化的解决问题,这于双反而言都是最有的做法,换位思考在团队合作中也是我学到的一个很重要的思想。由于前后端的实现内容不一样,所以偶尔在后台开发遇到难题时,也会跟同伴吐槽一下,他也会给我出谋划策,但总归而言,由于项目的分离,所以我们还是分工比较明确的,在各自的工作方面也无法给与太多的意见。
关于项目本身,本次我觉得最难处理同时也是收获比较大的是栈处理,根据符号的优先级,以及括号的优先级,将数据结构的思想运用到具体的项目中,是我比较大的一个收获。这个难题也是纠结了我很久,并且有参考网上关于栈的操作,最后处理出来,将各种边界条件考虑清楚,从而实现基本功能