130L

导航

结对项目

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024
这个给作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024/homework/13137
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序

合作人员

姓名 学号
黄冬炫 3122004570
盘伟铖 3122004579

一、github链接:https://github.com/sunwu12/project2.git

二、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
-Estimate -估计这个任务需要多少时间 20 20
Development 开发 785 847
-Analysis -需求分析 (包括学习新技术) 300 280
-Design Spec -生成设计文档 20 20
-Design Review -设计复审 20 22
-Coding Standard -代码规范 (为目前的开发制定合适的规范) 5 5
-Design -具体设计 150 180
-Coding -具体编码 220 260
-Code Review -代码复审 40 45
-Test -测试(自我测试,修改代码,提交修改) 30 35
Reporting 报告 43 55
-Test Repor -测试报告 15 20
-Size Measurement -计算工作量 10 15
-Postmortem & Process Improvement Plan -事后总结, 并提出过程改进计划 18 20
All 合计 848 927

三、效能分析

四、设计实现过程

类:

  1. Expression:表达式对象类,包含表达式字符串、表达式的值、表达式的主运算符等属性,用于构造复杂的且带有括号的表达式
    • splicing:拼接表达式。将两个表达式对象拼接成一个新的表达式对象。
    • addBrackets:生成括号。为表达式添加括号
    • getRandomValue:生成随机数。生成没有运算符的一个随机表达式对象
  2. Fraction:(真)分数对象类,包含分子、分母、分数数值等属性,用于构造一个可自动约分、化简的真分数
    • fractionCalculate:分数计算。计算两个分数的运算结果
    • transformValue:分数转换。根据输入的分子分母转换成Fraction对象
    • fractionSimplify:分数化简。化简约分分数对象
  3. ExpGeneration:表达式生成类,生成随机表达式
    • getExpression:生成一个随机的表达式(0<运算符个数<=3)
    • getAllExpression:生成指定数量的表达式集合
  4. ExpHandle:表达式处理类
    • calExpressionString:表达式计算。根据表达式字符串计算表达式的值
    • getInfixExpression:将表达式字符串转成中缀表达式
    • getPostfixExpression:将中缀表达式转换成后缀表达式
    • getSignPriority:获取运算符的优先级
    • handleList:将后缀表达式中的两个数值与一个运算符进行合并运算
    • checkDuplicate:判断两个表达式字符串是否重复,借助字符串的后缀表达式进行判断
  5. TxtHandle:文件操作类
    • txtRecord:将生成的随机表达式集合存入到题目文件和答案文件中
    • txtJudge:对题目文件和答案文件中的每一行题目和答案进行结果比对,并将结果写入Grade.txt文件中

五、代码说明

  1. 表达式拼接

    //拼接两个表达式成一个
    public static Expression splicing(Expression leftE,Expression rightE,char sign){
        if(leftE==null||rightE==null)return null;
        int newNum=leftE.num+rightE.num+1;
        Expression newE=new Expression();
        //运算符个数超过3个
        if(newNum> newE.maxNum)return null;
        if((newE.value=Fraction.fractionCalculate(leftE.value,rightE.value,sign))==null)return null;
        //添加括号
        if(sign=='×'||sign=='÷'){
            if(leftE.keySign=='+'||leftE.keySign=='-')addBrackets(leftE);
            if(rightE.keySign=='+'||rightE.keySign=='-')addBrackets(rightE);
        }
        if(sign=='÷'&&(rightE.keySign=='÷'||rightE.keySign=='×'))addBrackets(rightE);
        if(sign=='-'&&(rightE.keySign=='+'||rightE.keySign=='-'))addBrackets(rightE);
        newE.keySign=sign;
        newE.num=newNum;
        newE.expression=leftE.expression+' '+sign+' '+rightE.expression;
        return newE;
    }
    

    将两个表达式和一个运算符拼接成一个新的表达式,过程中会计算两个表达式的运算结果存入到新表达式的value中,并根据左右表达式的运算符添加括号

  2. 判断表达式是否重复

    public static Boolean checkDuplicate(String expression1, String expression2) {
        List<String> e1 = getPostfixExpression(expression1);
        List<String> e2 = getPostfixExpression(expression2);
        if (e1.size() != e2.size()) return false;
        if (!Objects.equals(calExpressionString(expression1), calExpressionString(expression2))) return false;
        String[] sinList1 = new String[2];
        String[] sinList2 = new String[2];
        do {
            e1 = handleList(e1, sinList1);
            e2 = handleList(e2, sinList2);
            if (!Arrays.equals(sinList1, sinList2)) return false;
            if (e1 == null || e2 == null) {
                break;
            }
        } while (e1.size() != 1);
        return true;
    }
    
    private static List<String> handleList(List<String> list, String[] sinList) {
            int i = 0;
            Pattern pattern = Pattern.compile("[+×÷-]");
            //每进行一次,去除一个运算符,合并两个数值
            while (i < list.size()) {
                String str = list.get(i);
                Matcher matcher = pattern.matcher(str);
                if (matcher.find()) {
                    char sign = str.charAt(0);
                    List<String> newList = new ArrayList<>(list);
                    String newVal = Fraction.fractionCalculate(newList.get(i - 2), newList.get(i - 1), sign);
                    if (newVal == null) return null;
                    sinList[0] = newList.get(i - 2);
                    sinList[1] = newList.get(i - 1);
                    Arrays.sort(sinList);
                    newList.set(i - 2, newVal);
                    newList.remove(i - 1);
                    newList.remove(i - 1);
                    return newList;
                }
                i++;
            }
            return null;
        }
        
      @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass()!= o.getClass()) return false;
            Expression exp = (Expression) o;
            return ExpHandle.checkDuplicate(this.expression,exp.expression);
        }
    

    先将两个表达式字符串都转成后缀表达式数组,再多次调用handleList方法将后缀表达式中的两个数值和一个运算符合成一个结果数值,其中sinList是由每次计算的两个值组合的数组,如果每次调用得到两个表达式的sinList有一次不相同,则视为两表达式不重复。最后重写Expression的equals方法,即可实现生成的随机表达式集合中没有重复的表达式

  3. 判断表达式结果对错



public static void txtJudge(String subjectPath,String answerPath) {
        List<String> subjectList=FileUtil.readUtf8Lines(subjectPath);
        List<String> anwerList=FileUtil.readUtf8Lines(answerPath);
        //将读取的表达式中的等于号去掉
        List<String> expList=subjectList.stream().map(s->s.replace(" =","")
                .split("^\\d+\\.")[1]).toList();
        List<String> valList=anwerList.stream().map(s->s.replace(" =","")
                .split("^\\d+\\.")[1].trim()).toList();
        int[] gradeList=new int[subjectList.size()];
        for(int i=0;i<subjectList.size();i++){
            //计算正确则为1,反之为0
            gradeList[i]= Objects.equals(ExpHandle.calExpressionString(expList.get(i)), valList.get(i)) ?1:0;
        }
        StringBuilder sb1=new StringBuilder();
        StringBuilder sb2=new StringBuilder();
        //根据1,0的个数判断题目正确个数
        long corNum=Arrays.stream(gradeList).filter(i->i==1).count();
        long wroNum=Arrays.stream(gradeList).filter(i->i==0).count();
        sb1.append("Correct: ").append(corNum).append("(");
        sb2.append("Wrong: ").append(wroNum).append("(");
        for(int i=0;i<subjectList.size();i++){
            if(gradeList[i]==1) {
                sb1.append(i + 1).append(",");
            }
            else sb2.append(i+1).append(",");
        }
        if(sb1.charAt(sb1.length()-1)==',')sb1.deleteCharAt(sb1.length()-1);
        if(sb2.charAt(sb2.length()-1)==',')sb2.deleteCharAt(sb2.length()-1);
        sb1.append(")");
        sb2.append(")");
        FileUtil.writeUtf8Lines(new ArrayList<>(List.of(new String[]{sb1.toString(),sb2.toString()})), GRADE);
    }

先读取题目文件中的表达式集合,并去除等于号,再根据calExpressionString方法逐条计算表达式字符串的结果是否与答案文件中的结果相同,相同记为1,不相同记为0;接着根据1,0数量计算题目的正确、错误个数并写入到grade.txt文件中

六、测试运行

@Test
public void testMain(){
    //获取随机表达式集合
    List<Expression> es= ExpGeneration.getAllExpression(30,20);
    for(Expression e : es){
        System.out.println(e);
    }
    //将表达式集合写入题目文件和答案文件中
    TxtHandle.txtRecord(es);
    //测试文件正确表达式个数
    try {
        TxtHandle.txtJudge("src/resources/Exercises.txt",
                "src/resources/Answers.txt");
    }catch (Exception e){
        System.out.println("文件格式不正确");
    }

}

其中2、5、8、15条数据人为修改过答案,故出现4个错误数

同时该项目可生成10000+不重复的表达式

七、项目小结

关于设计:原本是想着用后缀表达式来实现生成随机的表达式,但是过程中发现这样子不好生成括号,于是专门写了一个对象类用于实现生成带括号的表达式。
关于查重,因为是用对象来实现表达式,所以重写了对象的equals方法来实现判断俩个表达式是否重复,以确保生成的随机表达式当中不会出现重复的。
关于结对合作:在该结对项目中,我们学习了如何高效沟通,共同分析项目中的算法

posted on 2024-03-25 22:36  lkj1111  阅读(19)  评论(0编辑  收藏  举报