结对编程:四则运算生成器(郑邦坚,吴政毅)

作业要求

项目 内容
这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求
这个作业的目标 四则运算生成器项目+合作完成结对项目

作业所在Github地址

结对作业

结对项目成员(学号)

  • 吴政毅:3118005339
  • 郑邦坚:3118005349

项目思路分析

分数类

生成表达式的原理及思路

1.设计一个生成类,随机产生数和符号,生成后随机加括号,代码如下

public class Generator {
    private static final String[] OPERATOR = {"+", "-", "×", "÷"};
    private static final Random R = new Random();

    public static String generate(int maximum) {

        int cnt = R.nextInt(3) + 2;

        List<String> exp = new LinkedList<>();
        for (int i = 0; i < cnt; i++) {
            exp.add(String.valueOf(getFraction(maximum)));
            if (i != cnt - 1) {
                exp.add(OPERATOR[R.nextInt(4)]);
            }
        }
        if (R.nextInt(9) == 0) {
            List<Integer> index = new ArrayList<>();
            for (int i = 0; i < exp.size(); i += 2) {
                index.add(i);
            }
            int left = index.get(R.nextInt(index.size() - 1));
            int j = 0;
            for (int i = 0; i < index.size(); i++) {
                if (left < index.get(i)) {
                    index.set(j++, index.get(i) + 1);
                }
            }
            int right = index.get(R.nextInt(j)) + 1;
            exp.add(left, "(");
            exp.add(right, ")");//加括号
        }
        StringBuilder sb = new StringBuilder();
        for (String s : exp) {
            sb.append(s).append(" ");
        }
        return sb.toString();
    }

    public static Fraction getFraction(int maximum) {
        //调整随机数为整数或者分数
        boolean isFraction = R.nextBoolean();
        return isFraction ? new Fraction(R.nextInt(maximum) + 1, R.nextInt(maximum) + 1) : new Fraction(R.nextInt(maximum) + 1, 1);
    }
}

生成10000的题目如下:

去重

我们一开始想的是将整个表达式进行存储,每次生成新的式子进行比对,但随着表达式的增多,计算量和空间开销都很大,后来我们想用set集合本身具有的去重的特点去去重,但会存在无法区别1+2+3和3+2+1等类型的情况,我们采取了最简单粗暴的方法,将结果相同的式子删除,代码如下:

计算原理

通过逆波兰表达式的方法计算,逆波兰需要将中缀表达式转为后缀表达式,故书写一个类将将中缀表达式转为后缀表达式,代码如下:

public class Suffix {
    public static Map<String, Integer> opValue = new HashMap<>();

    static {
        opValue.put("×", 1);
        opValue.put("÷", 1);
        opValue.put("+", 0);
        opValue.put("-", 0);
    }
    public static String toSuffixExp(String exp) {
        StringBuilder nums = new StringBuilder();
        Stack<String> ops = new Stack();

        for (String s : exp.split(" ")) {
            if (opValue.containsKey(s)) {
                while (!ops.isEmpty() && !ops.peek().equals("(") && (opValue.get(ops.peek()) >= opValue.get(s))) {
                    nums.append(ops.pop()).append(" ");
                }
                ops.push(s);
            } else if (s.equals("(")) {
                ops.push(s);
            } else if (s.equals(")")) {
                while (!ops.isEmpty()) {
                    if (ops.peek().equals("(")) {
                        ops.pop();
                        break;
                    } else {
                        nums.append(ops.pop()).append(" ");
                    }
                }
            } else {
                nums.append(s).append(" ");
            }
        }
        while (ops.size() > 0) {
            nums.append(ops.pop()).append(" ");
        }
        return nums.toString();
    }
}

计算类代码

改为后缀表达式后,进行计算,但由于分数的存在,我们需要区分整数和分数的运算,代码如下:

public class Calculate {
    public String calculate(String exp){
        Stack<String> st = new Stack<>();
        for(String s: exp.split(" ")){
            if(Suffix.opValue.containsKey(s)){//是运算符取出栈顶的两个元素进行运算并将结果压入栈
                String result;
                String fir = st.pop();
                String sec = st.pop();
                if(isFraction(fir) || isFraction(sec)){
                    result = fractionCalculate(strToFraction(sec), strToFraction(fir), s);
                }else {
                    result = Calculate(Integer.parseInt(sec), Integer.parseInt(fir), s);
                }
                // 表达式错误
                if(result.equals("-1")) {
                    st.clear();
                    return "ERROR";
                }
                st.push(result);//结果入栈顶
            }else{
                st.push(s);//操作数入栈
            }
        }
        return st.pop();
    }
 
    private boolean isFraction(String s){
        return s.contains("/");
    }

    public Fraction strToFraction(String s){
        int numerator, denominator;
        if (s.contains("'")){
            denominator = Integer.parseInt(s.split("/")[1]);
            String[] split = s.split("/")[0].split("'");
            numerator = Integer.parseInt(split[0]) * denominator + Integer.parseInt(split[1]);
        }else if(s.contains("/")){
            //真分数
            numerator = Integer.parseInt(s.split("/")[0]);
            denominator = Integer.parseInt(s.split("/")[1]);
        }else{
            //整数
            numerator = Integer.parseInt(s);
            denominator = 1;
        }
        return new Fraction(numerator, denominator);
    }
    /*整数运算*/
    public String Calculate(int num1, int num2, String op){
        int result = -1;
        switch (op){
            case "+":
                result = num1 + num2;
                break;
            case "-":
                if(num1 >= num2){
                    result = num1 - num2;
                }
                break;
            case "×":
                result = num1 * num2;
                break;
            case "÷":
            default:
                // 分母不能为0
                if(num2 == 0) return String.valueOf(-1);
                if(num1 % num2 == 0){
                    result = num1 / num2;
                }else {
                    return new Fraction(num1, num2).toString();
                }

        }
        return String.valueOf(result);
    }
    
    /*分数运算*/
    public String fractionCalculate(Fraction fraction1, Fraction fraction2, String op){
        // 获取分数的分子、分母
        int Numerator1 = fraction1.getNumerator();
        int Denominator1 = fraction1.getDenominator();
        int Numerator2 = fraction2.getNumerator();
        int Denominator2 = fraction2.getDenominator();
        // 新分数的分子、分母
        int Numerator3, Denominator3;
        switch (op){
            case "+":
                Numerator3 = Numerator1  * Denominator2 + Numerator2 * Denominator1;
                Denominator3 = Denominator1 * Denominator2;
                break;
            case "-":
                //计算过程不能产生负数
                if((Numerator3 = Numerator1  * Denominator2 - Numerator2 * Denominator1) < 0){
                    return String.valueOf(-1);
                }else{
                    Denominator3 = Denominator1 * Denominator2;
                }
                break;
            case "×":
                Numerator3 = Numerator1  * Numerator2;
                Denominator3 = Denominator1 * Denominator2;
                break;
            case "÷":
            default:
                Numerator3 = Numerator1  * Denominator2;
                Denominator3 = Denominator1 * Numerator2;
                //分母不能为0
                if(Denominator3 == 0) return String.valueOf(-1);
        }


        if(Numerator3 % Denominator3 == 0){//运算结果为整数
            return String.valueOf(Numerator3 / Denominator3);
        }
        return new Fraction(Numerator3, Denominator3).toString();
    }

}

文件流的输入输出类和统计成绩结果输出

文件流的输入输出相对简单,统计成绩结果原理则是计算式子的答案,并与输入的答案进行比对,代码如下:

测试的结果如下:



改错1.2题的答案

测试类的代码:

public class unitTest {

    private Calculate calculate = new Calculate();

    /**
     * 测试生成题目
     */
    @Test
    public void testGenerator(){
        System.out.println(Generator.generate(10));
    }

    /**
     * 测试写入文件
     */
    @Test
    public void testWriteFile(){
        FileUtil.write("Exercises.txt", Generator.generate(10));
    }

    /**
     * 测试分数四则运算
     */
    @Test
    public void testFractionArithmetic(){
        Fraction f1 = new Fraction(1, 3);
        Fraction f2 = new Fraction(1, 4);
        String operator = "÷";
        String s = calculate.fractionCalculate(f1, f2, operator);
        assert(s.equals("1'1/3"));
    }

    /**
     * 测试将中缀表达式转换为后缀表达式
     */
    @Test
    public void testToSuffixExp(){
        String exp = "9 - 4 × ( 2 ÷ 5 )";
        assert(Suffix.toSuffixExp(exp).equals("9 4 2 5 ÷ × -"));
    }

    /**
     * 测试计算类
     */
    @Test
    public void testCalculate(){
        String exp = "9 - 4 × ( 2 ÷ 5 )";
        String s = Suffix.toSuffixExp(exp);
        assert(calculate.calculate(s).equals("7'2/5"));
    }

健壮性

错误输入会报错

性能分析:




PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 37
Estimate 估计这个任务需要多少时间 30 23
Development 开发 500 600
Analysis 需求分析(包括学习新技术) 60 80
Design Spec 生成设计文档 40 79
Design Review 设计复审 20 17
Coding Standard 代码规范(为目前的开发制定合适的规范) 30 40
Design 具体设计 30 45
Coding 具体编码 20 22
Code Review 代码复审 30 21
Test 测试(自我测试,修改代码,提交修改) 30 42
Reporting 报告 30 45
Test Repor 测试报告 30 40
Size Measurement 测试工作量 20 45
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 20 30
Total 合计 920 1166

总结

优点

1.经过这个结对项目,我们不再只是局限于原先的个人项目的各自开发,而是作为一个团队来一块开发这个项目。结对编程让我们的工作更有动力,能够集思广益减少犯错误的几率
2.遇到问题时,能够彼此有不同的思路,如我们遇到去重的问题时,我们提出了方案,但实用性并不好,这是另一种思路就显得格外的重要,从而有1+1>2的效果

缺点与不足

1.因为线下能够一起编码,且时间比较赶,就比较少使用git的版本迭代工具
2.没有形成属于自己的编码规范,这也与第一点有关,因为线下,几乎编码两个人都是能够理解的就没有形成较好的规范,后续可以通过codeReview来弥补一下

posted @ 2020-10-12 23:34  范克里夫  阅读(176)  评论(0编辑  收藏  举报