结对项目:简易实现自动生成四则运算

发布时间 2023-09-27 14:36:50作者: HelpmeOOUT

结对项目:简易实现自动生成四则运算

软件工程 https://edu.cnblogs.com/campus/gdgy/CSGrade21-12
作业要求 个人项目
作业目标 实现一个自动生成小学四则运算题目的命令行程序
github链接 https://github.com/HelpmeOOUT/RWL/tree/main/FourOperation-master

团队成员

姓名 学号
容伟亮 3121005006
龚杨 3121004994

PSP

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

具体代码

项目结构接口

Main函数

public class Main {
private static Integer r = null;
private static Integer n = null;
private static String e = null;
private static String a = null;
public static void main(String[] args) {
    processArgs(args);
    if(e != null && a != null){
        //分析答案
        analyse(e, a, "Grade.txt");
    }else if(r != null && n != null){
        //生成题目
        generateExercise(n, r);
    }else{
        System.out.println("参数错误,请重新启动并输入正确参数");
    }
}

/**
 * @param exercisePath 内含表达式的文件路径
 * @param answerPath 存放计算结果的文件路径
 * @param gradePath 统计对错情况及对应题目序号的文件路径
 * */
public static void analyse(String exercisePath, String answerPath, String gradePath){
    List<Equation> exercises = FileUtil.readAsEquation(exercisePath);
    List<Float> answers = FileUtil.readAsAnswer(answerPath);
    if(exercises.size() != answers.size()){
        System.out.println("练习与答案数量不相同,只分析写了答案的题目");
    }

    //记录对错情况
    List<String> corrects = new ArrayList<>();
    List<String> wrongs = new ArrayList<>();
    for(int i=0;i<answers.size();i++){
        if(Math.abs(answers.get(i) - exercises.get(i).getResult()) < 0.000001){
            corrects.add(i+"");
        }else{
            wrongs.add(i+"");
        }
    }
    StringBuilder sb1 = new StringBuilder();
    sb1.append("Correct:").append(corrects.size()).append("(");
    sb1.append(String.join(",", corrects)).append(")");
    StringBuilder sb2 = new StringBuilder();
    sb2.append("Wrong:").append(wrongs.size()).append("(");
    sb2.append(String.join(",", wrongs)).append(")");
    FileUtil.write(gradePath, Arrays.asList(sb1.toString(), sb2.toString()));
}

/**
 * @param exerciseNum 所需生成的题目数量
 * @param valueLimitation 指定范围参数,操作数及操作数分母<valueLimitation
 * */
public static void generateExercise(int exerciseNum, int valueLimitation){
    List<Equation> list = new ArrayList<>();
    Random r = new Random();
    //随机生成表达式,并筛掉重复的,直到数量达到目标
    for(;list.size()<exerciseNum;) {
        int operandNo = r.nextInt(3) + 2;
        int operatorNo = operandNo - 1;
        int bracketsNo = 1;
        list.add(Equation.generate(operandNo, operatorNo, bracketsNo, 1, valueLimitation));
        Equation.filter(list);
    }
    //写入文件
    FileUtil.write("Exercises.txt", list.stream().map(Equation::toString).toList());
    FileUtil.write("Answers.txt", list.stream().map(e -> e.getResult() + "").toList());
}

/**
 * @param args 命令行参数数组
 * */
public static void processArgs(String[] args){
    for(int i=0;i< args.length;i++){
        switch (args[i]){
            //可能有输入错误
            case "-r" -> r = Integer.parseInt(args[i+1]);
            case "-n" -> n = Integer.parseInt(args[i+1]);
            case "-e" -> e = args[i+1];
            case "-a" -> a = args[i+1];
        }
    }
}

/**
 * @param equation1 表达式1
 * @param equation2 表达式2
 * @return 返回两表达式是否重复
 * */
public static boolean checkRepetition(Equation equation1, Equation equation2){
    return true;
}
}

io流代码

/**
 * 文件工具类
 * @author 3121005035, OuroborosNo2
 * */
public class FileUtil {
    /**
     * 将内容写入指定路径文件
     * @param path 文件路径
     * @param context 写入内容
     * @return 成功与否
     * */
public static boolean write(String path, List<String> context){
    try {
        List<String> list = new ArrayList<>();
        //加上序号
        for(int i=0;i<context.size();i++) {
            list.add(i, i + "、" + context.get(i));
        }
        Files.write(Paths.get(path), list);
    }catch (Exception e){
        e.printStackTrace();
        return false;
    }
    return true;
}
/**
 * 从指定路径文件读取成表达式数组
 * @param path 文件路径
 * @return 表达式数组
 * */
public static List<Equation> readAsEquation(String path){
    List<Equation> lines;
    try {
        lines = Files.readAllLines(Paths.get(path)).stream().map(s -> {
            try {
                //除去序号
                return Equation.parse(s.substring(s.indexOf("、")+1));
            } catch (OperandException e) {
                throw new RuntimeException(e);
            }
        }).toList();
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }

    return lines;
}
public static List<Float> readAsAnswer(String path){
    List<Float> lines;
    try {
        //除去序号
        lines = Files.readAllLines(Paths.get(path)).stream().map(s -> Float.parseFloat(s.substring(s.indexOf("、")+1))).toList();
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }

    return lines;
}
}  

关键代码
用来过滤重复表达式,按顺序层层筛选,由于转换成后缀表达式,不用考虑括号

  1. 先去除运算过程含负数的

  2. 先比较结果

  3. 比较表达式是否一样

  4. 再比较包含的运算符是否相同

  5. 比较第一次运算的两数是否只是交换位置

       public static List<Equation> filter(List<Equation> list){
       for(int i=0;i < list.size();i++){
        Equation equation = list.get(i);
        //如果运算过程含负数,则跳过
        if(equation.isOf()){
            list.remove(equation);
            //remove会整体前移
            i--;
            continue;
        }
        //和整个list比较
        //标签方便下面层层嵌套能直接goto出来
        flag:
        for(int o=0;o< list.size();o++){
            Equation toCompare = list.get(o);
            //删除后有空位,要跳过
            if(toCompare == null){
                continue;
            }
            //遇到自己就跳过
            if(equation == toCompare){
                continue;
            }
            //先比较结果
            if(Math.abs(equation.getResult() - toCompare.getResult()) < 0.000001) {
                //结果相同,看是否完全一样
                if(equation.equals(toCompare)){
                    list.remove(equation);
                    //remove会整体前移
                    i--;
                    break flag;
                }
                //再比较运算符
                List<Arithmetic> postfix1 = equation.getPostfix();
                List<Arithmetic> postfix2 = toCompare.getPostfix();
                List<Operator> operators1 = equation.getOperators();
                List<Operator> operators2 = toCompare.getOperators();
                //有不同运算符就保留
                if(operators1.size() != operators2.size()){
                    break flag;
                }
                for(int j=0;j<operators1.size();j++){
                    if(operators1.get(j) != operators2.get(j)){
                        break flag;
                    }
                }
    
                //运算符相同,只比较第一次计算的两数字是否交换位置
                //找到第一个运算符,取前两个数字
                List<Operand> operands1 = new ArrayList<>();
                List<Operand> operands2 = new ArrayList<>();
                for(int j=0;j<postfix1.size();j++){
                    if(postfix1.get(j) instanceof Operator){
                        operands1.add((Operand) postfix1.get(j-1));
                        operands1.add((Operand) postfix1.get(j-2));
                        break;
                    }
                }
                for(int j=0;j<postfix1.size();j++){
                    if(postfix2.get(j) instanceof Operator){
                        operands2.add((Operand) postfix2.get(j-1));
                        operands2.add((Operand) postfix2.get(j-2));
                        break;
                    }
                }
                //比较两对数字
                if((operands1.get(0).equals(operands2.get(0)) || operands1.get(0).equals(operands2.get(1)))
                        && (operands1.get(1).equals(operands2.get(0)) || operands1.get(1).equals(operands2.get(1)))){
                    list.remove(equation);
                    //remove会整体前移
                    i--;
                    break flag;
                }else{
                    //两对数字不相同,保留
                    break flag;
                }
            }else{
                //结果不一样,保留
                break flag;
            }
        }
    
    }
    return list.stream().toList();
    }
    

用来生成随机表达式
@param operandNo 操作数数量
@param operatorNo 运算符数量
@param bracketsNo 括号数量
@param lowEnd 生成表达式中操作数和真分数分母范围的下限
@param upEnd 生成表达式中操作数和真分数分母范围的上限
@return 生成的表达式

   public static Equation generate(int operandNo, int operatorNo, int bracketsNo
        , int lowEnd, int upEnd){
    Random r = new Random();
    int scope = upEnd - lowEnd;
    List<Arithmetic> arithmetics = new ArrayList<>();
    List<Operand> operands = new ArrayList<>();
    List<Operator> operators = new ArrayList<>();
    List<Brackets> brackets = new ArrayList<>();

    try {
        for (int i = 0; i < operandNo; i++) {
            // 操作数类型 自然数(0),真分数(1)
            int type = r.nextInt(10)%2;
            if(0 == type){
                //生成随机整数
                operands.add(new Operand(type, r.nextInt(scope) + lowEnd + ""));
            }else if (1 == type){
                //生成真分数
                int denominator = r.nextInt(scope) + lowEnd + 1;
                // 分子 > 0
                int numerator = r.nextInt(denominator - 1) + 1;
                String str = numerator + "/" + denominator;
                operands.add(new Operand(type, str));
            }
        }

        for (int i = 0; i < operatorNo; i++) {
            // 除去等号
            int index = r.nextInt(4) + 1;
            operators.add(Operator.getByIndex(index));
        }

        for (int i = 0; i < bracketsNo; i++) {
            brackets.add(Brackets.getByIndex(0));
            brackets.add(Brackets.getByIndex(1));
        }

        for (int i = 0; i < operands.size(); i++) {
            if(operands.get(i) != null){
                arithmetics.add(operands.get(i));
            }
            if(i == operands.size()-1){
                break;
            }
            if(operators.get(i) != null) {
                arithmetics.add(operators.get(i));
            }
        }

       


    }catch (Exception e){
        e.printStackTrace();
        return null;
    }
    return new Equation(arithmetics);
}

将中缀表达式转换为后缀表达式
@return 返回转换结果

public List<Arithmetic> infixToPostfix(){
    Stack<Arithmetic> stack = new Stack<>();
    List<Arithmetic> postfix = new ArrayList<>();
    for(int start = 0; start < infix.size(); start++){
        //如果是运算符
        if(infix.get(start).priority > 0) {
            //栈空 或 "(" 或 符号优先级>栈顶符号 且 不为")" 直接进栈
            if (stack.isEmpty() || infix.get(start).priority == 3 ||
                    ((infix.get(start).priority > stack.peek().priority) && infix.get(start).priority < 4)) {
                stack.push(infix.get(start));
            } else if (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
                //栈非空 且 符号优先级≤栈顶符号, 出栈; 直到 栈为空 或 遇到了"("
                while (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
                    if (stack.peek().priority == 3) {
                        stack.pop();
                        break;
                    }
                    postfix.add(stack.pop());
                }
                stack.push(infix.get(start));
            } else if (infix.get(start).priority == 4) {
                //")",依次出栈直到空栈或遇到第一个"(",此时"("出栈
                while (!stack.isEmpty()) {
                    if (stack.peek().priority == 3) {
                        stack.pop();
                        break;
                    }
                    postfix.add(stack.pop());
                }

            }
        }else if(infix.get(start).priority == -1){
            postfix.add(infix.get(start));
        }
    }
    while(!stack.isEmpty()){
        postfix.add(stack.pop());
    }
    return postfix;
}

性能分析

测试运行


项目小结

这次项目对我们来说还是非常艰巨的,而且本次要求的结对编程我们也是首次接触。在完成该次项目的过程中,我们查阅了很多资料,逐步的完成一个个功能,期间遇到了很多的难题,但好在最后都得以解决了。发现合作编程需要队友间进行多交流沟通,好的合作可以大大提高开发效率。其次,还有一些需要改进的地方,例如项目计划和时间管理方面还有待提高,代码质量方面还有需要改进的地方等等。