结对编程(先后端分离)

成员:前端

  巫培杰: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类是提供接口的类,内部包含了三个函数,分别是indexgeneratedownload,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,因此两我的有时也会一块儿讨论交接在哪里出问题了。因为这个项目是和我舍友一块儿进行的,因此在讨论起来是会比较方便,同时也体会到了团队合做中和谐的重要性,只有耐心的听取他人的意见,才可能最优化的解决问题,这于双反而言都是最有的作法,换位思考在团队合做中也是我学到的一个很重要的思想。因为先后端的实现内容不同,因此偶尔在后台开发遇到难题时,也会跟同伴吐槽一下,他也会给我出谋划策,但总归而言,因为项目的分离,因此咱们仍是分工比较明确的,在各自的工做方面也没法给与太多的意见。

  关于项目自己,本次我以为最难处理同时也是收获比较大的是栈处理,根据符号的优先级,以及括号的优先级,将数据结构的思想运用到具体的项目中,是我比较大的一个收获。这个难题也是纠结了我好久,而且有参考网上关于栈的操做,最后处理出来,将各类边界条件考虑清楚,从而实现基本功能

相关文章
相关标签/搜索