Java实现自动生成小学四则运算题目的命令行程序

发布时间 2023-09-30 01:30:26作者: 星芒Ast

项目Github仓库链接

这个作业属于哪个课程 软件工程
这个作业要求在哪里 个人项目
这个作业的目标 实现一个自动生成简单四则运算题目并进行计算的程序,同时提供核对答案是否正确的功能

一、PSP表格

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

二、计算模块接口的设计与实现过程

模块接口


  Fraction类是分数类,包括分子和分母两个数据类型,以及分数相加、相减、相乘、化简和求分子分母最大公约数的函数。Calculator类
继承自Fraction类,两个主要函数为integerCalculator和fractionCalCaltor,分别实现整数运算和分数运算。OPerationsCreate类以中缀表达式的
形式生成一个符合该项目要求的四则运算算式。PostfixExpression类可以将OPerationsCreate类中生成的算式转换成便于程序计算的后缀表达式。而
txtOperation类实现文件的读取与写入,同时可以匹配一组题目文件和答案文件,检查答案文件中各个算式的答案的正确与否。

关键函数

整数计算

点击查看代码
public static int integerCalculator(List<String> equation){//整数计算
        Stack<String> s1=new Stack<>();
        int result=0;
        for (String s : equation) {
            if (!PostfixExpression.isOperator(s)) {
                s1.push(s);//逐个扫描字符,若为数字则直接入栈
            } else {//若为符号则弹出两个数字与当前符号进行运算,并将结果入栈
                result = Calculate(s1.pop(), s1.pop(), s);
                s1.push(String.valueOf(result));
            }
        }
        return  result;

private static int Calculate(String a,String b,String c){//整数计算
        int result=0;
        int ai=Integer.parseInt(a);
        int bi=Integer.parseInt(b);
        switch(c) {//根据传入的符号进行相应的计算
            case "+" -> result = ai + bi;
            case "-" -> result = bi - ai;//对于减法,应考虑两个数字入栈的顺序
            case "*" -> result = ai * bi;
        }
        return result;
    }

分数计算

点击查看代码
public static String fractionCalculator(List<String> equation){//分数计算
        for(int j=0;j<2;j++){
            if(equation.contains("'")){//判断分数形式,若为带"'"的真分数形式则化简为假分数形式
                int temp=equation.indexOf("'");//返回"'"所在的下标
                int t1=Integer.parseInt(equation.get(temp-1));//获取"'"前后的数字
                int t2=Integer.parseInt(equation.get(temp+1));
                int t3=Integer.parseInt(equation.get(temp+2));
                int t4=t1*t3+t2;//将"'"前的数字乘以分母后与分子相加
                equation.remove(temp+1); equation.remove(temp);
                equation.set(temp-1,String.valueOf(t4));
            }
        }
        int i=0;
        String n1= equation.get(i++);//提取两个分数的分子与分母
        String d1= equation.get(i++);
        i++;//跳过符号
        String n2= equation.get(i++);
        String d2= equation.get(i++);
        i++;
        return fractionCalculate(d1,n1,d2,n2,equation.get(i));
    }

private static String fractionCalculate(String a1,String b1,String a2,String b2,String c){//分数具体计算
        Fraction f1=fractionCreate(a1,b1);//根据传入的分子分母数据构造分数
        Fraction f2=fractionCreate(a2,b2);
        switch (c){//根据符号选择对应的计算
            case "+"->f1= f1.addFraction(f2);
            case "-"->f1=f1.subFraction(f2);
            case "*"->f1=f1.multFraction(f2);
        }
        return f1.numerator+"/"+f1.denominator;//以分数形式返回结果
    }

中缀表达式转后缀表达式

点击查看代码
public static List<String> postfixChange(String fix){//中缀表达式转后缀表达式
        Stack<String> s1=new Stack<>();
        List<String> s2=new ArrayList<>();
        List<String> infix=toList(fix);
        int size=infix.size();
        int priority;
        int i=0;
        while(i<size){
            if(isOperator(infix.get(i))){//如果该元素不是数字
                if(infix.get(i).compareTo("(")==0){//左括号无条件入栈s1
                    s1.push(infix.get(i));
                    i++;
                    continue;
                }
                else if(infix.get(i).compareTo(")")==0){
                    while(s1.peek().compareTo("(")!=0){//当碰见右括号时依次让s1栈顶的元素出栈并加入到s2,直到栈顶元素为左括号
                        s2.add(s1.pop());
                    }
                    if(Objects.equals(s1.peek(), "("))//左括号出栈
                        s1.pop();
                    i++;
                    continue;
                }
                priority=getPriority(infix.get(i));//若不是左括号,则计算该符号的优先级
                if(s1.isEmpty() || priority > getPriority(s1.peek())){//若栈空或优先级高于栈顶元素优先级,则直接入栈
                    s1.push(infix.get(i));
                }
                else{
                    while (!s1.isEmpty() && priority <= getPriority(s1.peek()))
                    {
                        s2.add(s1.pop());//若优先级小于栈顶则栈顶出栈并加入s2,直到优先级大于栈顶或栈空
                    }
                    s1.push(infix.get(i));
                }
            }
            else{
                s2.add(infix.get(i));//若为数字则加入s2
            }
            i++;
        }
        while (!s1.isEmpty()) {//将s1剩余元素加入到s2
            s2.add(s1.pop());
        }
        return s2;
    }

算式生成

点击查看代码
public static List<String> operationsCreate(int n, int r){
        Random random=new Random();
        List<String> op=new ArrayList<>();
        int t;
        String operation;
        String[] symbol ={" "," "," "};//一个算式最多包含三个符号
        for(int i=0;i<n;i++){
            t= random.nextInt(4);//根据随机数的结果选择生成何种算式
            if(t<2){//随机数结果小于2,生成只包含整数的算式
                for(int j=0;j<3;j++){
                    t= random.nextInt(3);//根据随机数结果生成符号
                    if(t==0)
                        symbol[j]="+";
                    else if(t==1)
                        symbol[j]="-";
                    else
                        symbol[j]="*";
                }
                t=random.nextInt(5);//根据随机数结果生成某种形式的算式
                if(t==0)
                    //生成包含两个数字一个符号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1);
                else if(t==1)
                    //生成包含三个数字两个符号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " " + symbol[1] + " " + (random.nextInt(r)+1);
                else if(t==2)
                    //生成包含四个数字三个符号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " " + symbol[1] + " " + (random.nextInt(r)+1) + " " +
                            symbol[2] + " " + (random.nextInt(r)+1);
                else if(t==3)
                    //生成包含四个数字三个符号且后两个数字带有括号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " " + symbol[1] + " " + "( " + (random.nextInt(r)+1) + " " +
                            symbol[2] + " " + (random.nextInt(r)+1) + " )";
                else
                    //生成包含四个数字三个符号且前两个数字带有括号的算式
                    operation = "( " + (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " ) " + symbol[1] + " " + (random.nextInt(r)+1) + " " +
                            symbol[2] + " " + (random.nextInt(r)+1);
            }
            else{//生成只包含分数的算式
                symbol[0]="/"; symbol[2]="/";
                t=random.nextInt(3);
                switch (t){
                    case 0->symbol[1]="+";
                    case 1->symbol[1]="-";
                    case 2->symbol[1]="*";
                }
                t=random.nextInt(3);
                if(t>0)
                    //生成包含两个分数一个符号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " " + symbol[1] + " " + (random.nextInt(r)+1) + " " +
                        symbol[2] + " " + (random.nextInt(r)+1);
                else
                    //生成包含"'"、两个分数和一个符号的算式
                    operation = (random.nextInt(r)+1) + " " + symbol[0] + " " +
                            (random.nextInt(r)+1) + " " + symbol[1] + " " + (random.nextInt(r)+1) + " ' " +
                            (random.nextInt(r)+1)+ " " +  symbol[2] + " " + (random.nextInt(r)+1);

            }
            op.add(operation);
        }
        return op;
    }

效能分析

总览


分析

  由于本项目生成的算式、计算出的答案等数据均以字符串的形式保存,并以List的形式管理,所以在内存开销上byte类型和String类占用的最多。
设计之初计划将数字与运算符号分开存储,但实现起来较为复杂,且需要来回读取不同数据类型的List表,不能做到一次读取和一次写入,所以重新考虑,将整
个算数都以字符串的形式保存,另写一个函数用来辨别一个元素是否为数字。

单元测试

1.txtOperation类测试模块

通过打开正确文件、错误格式文件、错误路径文件的方式测试能否正常读写文件;通过对手动输入指定文档测试文件的读取与写入功能。

点击查看代码
public class txtOperationTest {

    @Test
    public void read() throws IOException {
        List<String> t1= txtOperation.Read("F:\\program\\c\\software\\txt\\1.txt");
        System.out.println(t1);
        //读取不存在的文件
        List<String> t2=txtOperation.Read("F:\\program\\c\\software\\txt\\111.txt");
        //读取错误格式的文件
        List<String> t3=txtOperation.Read("F:\\program\\c\\software\\txt\\");
    }

    @Test
    public void writeOperations() throws IOException {
        List<String> op= List.of(new String[]{"1", "2", "3"});
        txtOperation.writeOperations(op);
    }

    @Test
    public void writeAnswers() throws IOException {
        List<String> answer= List.of(new String[]{"4", "5", "6"});
        txtOperation.writeOperations(answer);
    }

    @Test
    public void answersMatch() throws IOException {
        List<String> answer= List.of(new String[]{"1.  4", "2.  5", "3.  6"});
        txtOperation.answersMatch(answer,"F:\\program\\Java\\ideaWorkplace\\Operations\\1.txt");
    }

2.Fraction类测试模块

通过手动创建两个分数并让两个分数相互计算,检查结果是否正确。

点击查看代码
public class FractionTest {

    @Test
    public void addFraction() {
        Fraction f1=new Fraction(5,1);
        Fraction f2=new Fraction(5,2);
        f1=f1.addFraction(f2);
        System.out.println(f1.numerator + "/" + f1.denominator);
    }

    @Test
    public void subFraction() {
        Fraction f1=new Fraction(5,2);
        Fraction f2=new Fraction(5,4);
        f1=f1.subFraction(f2);
        System.out.println(f1.numerator + "/" +  f1.denominator);
    }

    @Test
    public void multFraction() {
        Fraction f1=new Fraction(5,1);
        Fraction f2=new Fraction(3,2);
        f1=f1.multFraction(f2);
        System.out.println(f1.numerator + "/" +  f1.denominator);
    }
}

3.###Calculator类测试
计算手动输入的后缀表达式,并检查结果是否正确。

点击查看代码
public class CalculatorTest {

    @Test
    public void integerCalculator() {
        List<String> op1= List.of(new String[]{"1", "2", "9", "*", "+"});
        List<String> op2= List.of(new String[]{"1", "2", "+", "9", "*"});
        List<String> op3= List.of(new String[]{"20", "9", "2", "-", "+"});
        System.out.println(Calculator.integerCalculator(op1));
        System.out.println(Calculator.integerCalculator(op2));
        System.out.println(Calculator.integerCalculator(op3));
    }

    @Test
    public void fractionCalculator() {
        List<String> op1= List.of(new String[]{"1", "5", "/", "3", "5", "/", "+"});
        List<String> op2= List.of(new String[]{"4", "5", "/", "3", "5", "/", "-"});
        List<String> op3= List.of(new String[]{"1", "5", "/", "3", "5", "/", "*"});
        System.out.println(Calculator.fractionCalculator(op1));
        System.out.println(Calculator.fractionCalculator(op2));
        System.out.println(Calculator.fractionCalculator(op3));
    }
}

4.OperationsCreate类测试

创建不同数目的算式。

点击查看代码
public class OperationsCreateTest {

    @Test
    public void operationsCreate() {
        System.out.println(OperationsCreate.operationsCreate(1,5));
        System.out.println(OperationsCreate.operationsCreate(5,10));
        System.out.println(OperationsCreate.operationsCreate(10,20));
    }
}

5.PostfixExpression类测试

手动输入计算符号或其他字符,检查能否正确判断。手动输入中缀表达式,检查函数能否正确转换为后缀表达式。

点击查看代码
public class PostfixExpressionTest {

    @Test
    public void isOperator() {
        if(PostfixExpression.isOperator("+"))
            System.out.println("该符号是计算符号");
        if(PostfixExpression.isOperator("-"))
            System.out.println("该符号是计算符号");
        if(PostfixExpression.isOperator("*"))
            System.out.println("该符号是计算符号");
        if(PostfixExpression.isOperator("/"))
            System.out.println("该符号是计算符号");
        if(!PostfixExpression.isOperator("3"))
            System.out.println("该符号不是计算符号");
        if(!PostfixExpression.isOperator("111"))
            System.out.println("该符号不是计算符号");
        if(!PostfixExpression.isOperator("ra"))
            System.out.println("该符号不是计算符号");
    }

    @Test
    public void postfixChange() {
        System.out.println(PostfixExpression.postfixChange("1 + 2 * 9"));
        System.out.println(PostfixExpression.postfixChange("( 1 + 2 ) * 9"));
        System.out.println(PostfixExpression.postfixChange("4 * 2 * 9"));
        System.out.println(PostfixExpression.postfixChange("5 - 2 * 9"));
        System.out.println(PostfixExpression.postfixChange("( 5 - 2 ) * 9"));
        System.out.println(PostfixExpression.postfixChange("1 / 5 + 2 / 5"));
        System.out.println(PostfixExpression.postfixChange("4 / 5 - 2 / 5 * 3 / 5"));
        System.out.println(PostfixExpression.postfixChange("( 4 / 5 - 2 / 5 ) * 3 / 5"));
    }
}

运行结果