结对项目:实现自动生成小学四则运算题目

发布时间 2023-09-28 18:46:44作者: l11322
这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/CSGrade21-12
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13016
这个作业的目标 实现小学四则运算的自动生成

Github链接

https://github.com/lsw11322/lsw11322/tree/main/3121004744/4operation

团队成员

姓名 学号
骆圣威 3121004744
李文浩 3121004788

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 40
· Estimate · 估计这个任务需要多少时间 30 40
Development 开发 660 845
· Analysis · 需求分析 (包括学习新技术) 60 80
· Design Spec · 生成设计文档 10 10
· Design Review · 设计复审 40 60
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 30
· Design · 具体设计 90 100
· Coding · 具体编码 270 360
· Code Review · 代码复审 20 25
· Test · 测试(自我测试,修改代码,提交修改) 150 180
Reporting 报告 170 200
· Test Report · 测试报告 120 150
· Size Measurement · 计算工作量 20 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
合计 860 1085

效能分析

  • 内存占用及CPU负载


    从图中可以看出,程序占用的内存较小,对内存容量要求不大,项目中char和String类型使用较多,char类型主要用于运算符的判断,String类型主要用于存放题目及文件路径。

设计实现过程

大致流程:

- CreateInteger类:创建整数运算式子
    String[] createProblem(int range):获取题目及其对应答案
    int[] index(int operatorCount, int operatorTotal, Random random):为运算符分配下标
    String stitchingFormula(int operatorCount, int[] operand, int[] operatorIndex):拼接整数与运算符并返回题目
- CreateFraction类:创建分数运算式子
    String[] createProblem(int range):获取题目及其对应答案
    int greatFraction(int x, int y):获取两个数的最大公因数
    int[] createCoprimeNumbers(int range, Random random):获取一对互质数
    String shamToProperFraction(int x, int y):获取假分数的真分数形式
- Producer类:创建Exercises.txt文件并存入题目
    void constructProblem():生成提示引导用户输入
    void generateProblem(int num, int range):创建Exercises.txt文件并调用outPutFile函数输入题目
    void outPutFile(int i, String[] problem, PrintStream... var):输入题目到指定文件
- Calculator类:
    int calculate(int a, int b, String stmp):返回加减乘除运算结果
    int algorithm(String str):返回整个式子的运算结果
- JudegeAnswer类:判断对错,输出答题情况到Grade.txt文件
    void getPath():获取作答文件和答案文件路径
    List<String> exerciseFileReader(String path):获取作答文件的作答结果
    List<String> answerFileReader(String path):获取答案文件的答案
    void check(tring exePath, String ansPath):对比作答结果和答案,输出答题情况

关键代码说明

public int algorithm(String str) {
        //放数字
        Stack<Integer> numStack = new Stack<>();
        //放操作符
        Stack<String> operatorStack = new Stack<>();
        //存放运算符优先级
        HashMap<String, Integer> hashMap = new HashMap<>();
        hashMap.put("(", 0);
        hashMap.put("+", 1);
        hashMap.put("-", 1);
        hashMap.put("*", 2);
        hashMap.put("÷", 2);
        //将算式中的空格去除
        String formula = str.replaceAll(" ", "");
        //逆波兰算法
        for (int i = 0; i < formula.length(); ) {
            StringBuilder digit = new StringBuilder();
            //将式子字符串切割为c字符
            char c = formula.charAt(i);
            //判断字符是否为数字,将一个数加入digit,遇到符号则停下
            while (Character.isDigit(c)) {
                digit.append(c);
                i++;
                if (i < formula.length()) {
                    c = formula.charAt(i);
                } else {
                    break;
                }
            }
            //当前digit里面已经无数字,即当前处理符号
            if (digit.length() == 0) {
                switch (c) {
                    case '(': {
                        //如果是(则转化为字符串压入字符栈
                        operatorStack.push(String.valueOf(c));
                        break;
                    }
                    //遇到右括号了计算,因为(的优先级最高
                    case ')': {
                        //如果是),将符号栈栈顶元素取到
                        String stmp = operatorStack.pop();
                        //当前符号栈里面还有+ - * /
                        while (!operatorStack.isEmpty() && !stmp.equals("(")) {
                            //取操作数a,b
                            int a = numStack.pop();
                            int b = numStack.pop();
                            //计算
                            int result = calculate(b, a, stmp);
                            //要求运算过程不能出现负数
                            if (result < 0) {
                                return -1;
                            }
                            //将结果压入栈
                            numStack.push(result);
                            //符号指向下一个符号
                            stmp = operatorStack.pop();
                        }
                        break;
                    }
                    //遇到等号了计算
                    case '=': {
                        String stmp;
                        //当前符号栈里面还有+ - * ÷,即还没有算完
                        while (!operatorStack.isEmpty()) {
                            stmp = operatorStack.pop();
                            int a = numStack.pop();
                            int b = numStack.pop();
                            int result = calculate(b, a, stmp);
                            if (result < 0) {
                                return -1;
                            }
                            numStack.push(result);
                        }
                        break;
                    }
                    default: {  //不满足之前的任何情况
                        String stmp;
                        //如果符号栈有符号
                        while (!operatorStack.isEmpty()) {
                            //当前符号栈,栈顶元素
                            stmp = operatorStack.pop();
                            //比较优先级
                            if (hashMap.get(stmp) >= hashMap.get(String.valueOf(c))) {
                                int a = numStack.pop();
                                int b = numStack.pop();
                                int result = calculate(b, a, stmp);
                                if (result < 0) {
                                    return -1;
                                }
                                numStack.push(result);
                            } else {
                                operatorStack.push(stmp);
                                break;
                            }

                        }
                        //将符号压入符号栈
                        operatorStack.push(String.valueOf(c));
                        break;
                    }
                }
            } else { //处理数字,直接压栈
                //Integer.valueof()返回的是Integer对象,而Integer.parseInt()返回的是int型
                numStack.push(Integer.valueOf(digit.toString()));
                //结束本次循环,回到for语句进行下一次循环,即不执行i++(因为此时i已经指向符号了)
                continue;
            }
            //
            i++;
        }
        //返回栈底数字即等式的答案。
        return numStack.peek();
    }

测试运行

  • 1.Producer类测试代码:
public class ProducerTest {
    Producer producer = new Producer();

    @Test
    //范围0~5,生成20个问题和对应答案测试
    public void test(){
        try {
            producer.generateProblem(20,5);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 测试结果:
    题目Exercises.txt文件:
1. 5 - 2'1/2 =
2. 1 - 1 + 1'1/2 + 1 =
3. 2/5 - 1/5 + 1'2/3 + 1/5 =
4. 3 * 2 =
5. 1/5 - 0 =
6. 4 ÷ 1 =
7. 4 + 1/2 + 1/3 - 2/5 =
8. 1'1/3 + 1'2/3 - 1 - 1/5 =
9. 1/2 + 2/5 - 2/5 - 1/5 =
10. 2'1/2 - 1'2/3 + 1'1/3 =
11. 0 + ( 1 * 4 ) =
12. 1 * 3 =
13. 0 + 1 =
14. 2 + 1/2 - 1 - 2/5 =
15. 3/5 - 2/5 + 2 =
16. 1/2 - 2/5 =
17. 1 + 2/3 - 1 =
18. 0 + 4 =
19. 1/5 + 1/2 - 1/2 + 1 =
20. 2/3 - 1/3 - 1/3 + 1'1/4 =

答案Answers.txt文件:

1. 2'1/2
2. 2'1/2
3. 2'1/15
4. 6
5. 1/5
6. 4
7. 4'13/30
8. 1'4/5
9. 3/10
10. 2'1/6
11. 4
12. 3
13. 1
14. 1'1/10
15. 2'1/5
16. 1/10
17. 2/3
18. 4
19. 1'1/5
20. 1'1/4
  • 2.JudgeAnswer类测试代码:
public class JudgeAnswerTest {
    JudgeAnswer judgeAnswer = new JudgeAnswer();

    @Test
    //测试全对的情况
    public void checkTest1(){
        judgeAnswer.check("D:\\SoftwareProject\\hk1\\3121004744\\4operation\\src\\test\\resources\\Exer.txt", "D:\\SoftwareProject\\hk1\\3121004744\\4operation\\src\\test\\resources\\Ans.txt");
    }

    @Test
    //测试文件路径不存在的情况
    public void checkTest2(){
        judgeAnswer.check("hahaha.txt", "hehehe.txt");
    }
}
  • 测试结果:

    • 输入的文件:

    • 输出的Grade.txt文件内容如下:

  • 3.Calculator类测试代码:

public class CalculatorTest {
    int num1 = 6;
    int num2 = 6;
    Calculator calculator = new Calculator();
    //加减乘除测试
    @Test
    public void calculateTest(){
        //加法
        int result = calculator.calculate(num1,num2,"+");
        System.out.println(num1 + "+" + num2 + "的结果是" + result);
        //减法
        result = calculator.calculate(num1, num2, "-");
        System.out.println(num1 + "-" + num2 + "的结果是" + result);
        //乘法
        result = calculator.calculate(num1, num2, "*");
        System.out.println(num1 + "*" + num2 + "的结果是" + result);
        //除法
        result = calculator.calculate(num1, num2, "÷");
        System.out.println(num1 + "÷" + num2 + "的结果是" + result);
    }

    //算式计算测试
    @Test
    public void algorithmTest(){
        int result = calculator.algorithm("6 * ( 8 + 8 ) = ");
        System.out.println("算式6 * ( 8 + 8 ) = " + result);
    }
}
  • 测试结果:

  • 4.CreateFraction类测试代码:

public class CreateFractionTest {
    CreateFraction createFraction = new CreateFraction();

    /**
     * 分数式子及其答案生成测试
     */
    @Test
    public void createProblemTest(){
        String[] str = createFraction.createProblem(10);
        System.out.println("生成式子:" + str[0]);
        System.out.println("对应答案:" + str[1]);
    }

    /**
     * 求最大公因数测试
     */
    @Test
    public void greatFractionTest(){
        int result = createFraction.greatFraction(666,66);
        System.out.println(666 + "和" + 66 + "的最大公因数是" + result);
    }

    /**
     * 互质数生成测试
     */
    @Test
    public void createCoprimeNumbersTest(){
        Random random = new Random();
        int[] nums = createFraction.createCoprimeNumbers(10, random);
        int num1 = nums[0];
        int num2 = nums[1];
        int result = createFraction.greatFraction(num1,num2);
        if(result == 1){
            System.out.println(num1 + "和" + num2 + "的最大公因数是1,两数为互质数");
        }else{
            System.out.println("该方法出现异常");
        }
    }

    /**
     * 假分数转化为真分数测试
     */
    @Test
    public void shamToProperFractionTest(){
        String str = createFraction.shamToProperFraction(3,2);
        System.out.println("3/2的真分数形式是" + str);
    }
}
  • 测试结果:

项目小结

本次项目的难度对我们来说是很大的,主要的问题之一发生在算法的设计和各个方法接口之间的协调,因此开发所花的时间较多;另一个主要的问题是团队内两个人在对代码设计的要求及方法的选取上会出现意见不一,但通过沟通,最终还是达成了一致。总体来说,有团队一起开发的工作量要比一个人单干的工作量要少很多。通过本次项目,我们对java基础中常用的类更加熟悉了,以及也意识到了自己在某些方面上还要加强的问题。