结对项目

概况

完成人员 学号
钟京洲 3121005063
唐梦思 3221005284

作业概述

软件工程 计科21级3班 + 4班
作业要求 四则运算生成
Github地址 仓库

PSP表格

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

项目

需求概况

需求描述 是否实现
控制生成题目的个数
控制题目中数值范围
计算过程不能产生负数,除法的结果必须是真分数,题目不能重复,运算符不能超过3个
生成的题目存入执行程序的当前目录下的Exercises.txt文件
题目的答案存入执行程序的当前目录下的Answers.txt文件
能支持一万道题目的生成
支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
统计结果输出到文件Grade.txt

需求分析

  1. 使用命令行操作,项目打包方式为jar包。
  2. 文件读写操作。
  3. 四则运算表达式生成、中缀表达式到后缀的转化。
  4. 去重

项目设计

项目概况

计算与生成表达式

Fraction

Fraction类为存储计算数的基本类,包含用辗转相除法求最大公约数,化假分数为带分数的方法,代码如下

点击查看代码
/**
 * @author zjz
 * @date 2023/9/23
 */
@Data
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class Fraction {

    /**
     * 分母默认为1
     */
    private int denominator;
    /**
     * 分子
     */
    private int numerator;

    /**
     * 用辗转相除法求最大公约数
     * @param a 被除数
     * @param b 除数
     * @return 最大公约数
     */
    private static int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }

    /**
     * 对分数进行约分
     */
    public void Appointment() {
        if (numerator == 0 || denominator == 1)
            return;
        // 如果分子是0或分母是1就不用约分了
        int gcd = gcd(numerator, denominator);
        this.numerator /= gcd;
        this.denominator /= gcd;
    }

    public  String transferFraction(){//对分数进行约分化简
        if (this.denominator == 0) this.denominator = 1;
        int c = this.numerator/gcd(this.numerator,this.denominator);
        int d = this.denominator/gcd(this.numerator,this.denominator);
        int e = c/d;
        int f = c%d;
        String str = "";
        if(f==0){
            str += e;
        }else if(e!=0){
            str = e+"'"+f+"/"+d;
        }else{
            str +=f+"/"+d ;
        }
        return str;
    }


    @Override
    public String toString() {
        return numerator + "/" + denominator;
    }
}

FractionUtil

包含加减乘除等一系列对Fraction的操作,以及将带分数转化为Fraction对象的方法

点击查看代码
public class FractionUtil {

    /**
     * 加法
     */
    public static Fraction add(Fraction f1, Fraction f2) {
        return new Fraction(f2.getDenominator() * f1.getDenominator(),f2.getNumerator() * f1.getDenominator() + f2.getDenominator() * f1.getNumerator());
    }

    /**
     * 减法
     */
    public static Fraction sub(Fraction f1, Fraction f2) {
        return new Fraction(f2.getDenominator() * f1.getDenominator(), f2.getDenominator() * f1.getNumerator() - f2.getNumerator() * f1.getDenominator());
    }

    /**
     * 分数的乘法运算
     */
    public static Fraction multiplication(Fraction f1, Fraction f2) { // 乘法运算
        return new Fraction(f2.getDenominator() * f1.getDenominator(),f2.getNumerator() * f1.getNumerator());
    }

    /**
     * 分数除法运算
     */
    public static Fraction div(Fraction f1, Fraction f2) {
        return new Fraction(f2.getNumerator() * f1.getDenominator(),f2.getDenominator() * f1.getNumerator());
    }

    /**
     * 带分数转化为Fraction对象
     * @param properFraction 带分数
     * @return Fraction
     */
    public static Fraction Transform(String properFraction,boolean type){

        if (type) {
            return new Fraction(1,Integer.parseInt(properFraction));
        } else {
            String[] split = properFraction.split("'");
            int denominator;
            int numerator;
            if (split.length == 1) {
                // 普通分数
                split = properFraction.split("/");
                denominator = Integer.parseInt(split[1]);
                numerator = Integer.parseInt(split[0]);

            } else {
                String[] split1 = split[1].split("/");
                denominator = Integer.parseInt(split1[1]);
                numerator = Integer.parseInt(split[0]) * denominator + Integer.parseInt(split1[0]);
            }

            Fraction fraction = new Fraction();
            fraction.setDenominator(denominator);
            fraction.setNumerator(numerator);

            return fraction;
        }
    }


}

CalculateImpl

  • 中缀转后缀时,采用一个队列和一个栈,数字输入到队列,运算符压入栈。在压入栈时,检查栈顶元素的优先级,要保证压入栈的元素的优先级最高。若栈顶元素大于等于想压入栈的元素,则把栈顶元素弹出,继续检查,直到被压入栈的元素的优先级是最高的。遇到"(“先入栈,遇到”)“把”("之后的元素弹出到队列,括号不输出到队列。
  • 后缀表达式求值时,遇到数字就压入栈,遇到运算符就弹出两个数字进行运算。因为最后的结果是分数,所以从一开始运算就把运算数全部转换为分数进行计算最后化简。
点击查看代码
public class CalculateImpl implements Calculate{


    @Override
    public Fraction fourKindCalculate(Fraction f1,Fraction f2, String operator) {
        Fraction result;
        switch (operator) {
            case "+":
                result = FractionUtil.add(f1,f2);
                break;
            case "-":
                result = FractionUtil.sub(f1,f2);
                break;
            case "*":
                result = FractionUtil.multiplication(f1,f2);
                break;
            case "/":
                // 除数不为0则返回结果,除数为0则报错
                if (f2.getDenominator() != 0) {
                    result = FractionUtil.div(f1,f2);
                } else {
                    throw new DivisionWrongException();
                }
                break;
            default:
                throw new WrongOperationException();
        }
        return result;
    }

    @Override
    public String calculate(String str) {
        String[] compute = str.split(" ");
        //存后缀表达式
        Stack<String> stack = new Stack<>();
        //放操作符
        Stack<String> optStack = new Stack<>();
        //放数字结果
        Stack<Fraction> numStack = new Stack<>();
        for (String s : compute) {
            // 带分数
            if (s.matches("^(\\d+)'(\\d+)\\/(\\d+)$")) {
               // 直接压入栈中,作为一个数来计算
                stack.push(s);
            }
            //匹配数字
            else if (s.matches("-?\\d+(\\.\\d+)?")) {
                stack.push(s);
            } else if (optStack.isEmpty()) {
                optStack.push(s);
            }
            //判断符号
            else if ("(".equals(s)) {
                optStack.push(s);
            } else if (")".equals(s)) {
                while (!"(".equals(optStack.peek())) {
                    stack.push(optStack.pop());
                }
                //找到(
                optStack.pop();
            }
            //加减乘除入栈
            else if ("+".equals(s) || "-".equals(s) || "*".equals(s)
                    || "/".equals(s)) {
                while ((!optStack.isEmpty()) && (PriorityTable.Priority(s) <= PriorityTable.Priority(optStack.peek()))) {
                    stack.push(optStack.pop());
                }
                optStack.push(s);
            }
            //数字小数点直接入栈
            else {
                stack.push(s);
            }
        }
        //当操作符栈不为空,将操作符全部弹入stack
        while (!optStack.isEmpty()) {
            stack.push(optStack.pop());
        }
        //numStack=
        Stack<Fraction> traversing = Traversing(stack, numStack);
        return traversing.pop().transferFraction();
    }

    @Override
    public Stack<Fraction> Traversing(Stack<String> stack,Stack<Fraction> numStack){
        for (String l : stack) {
            Fraction num1, num2;
            //正则表达式匹配分数
            if (l.matches("^(\\d+)(?:'(\\d+))?/(\\d+)$")){
                numStack.push(FractionUtil.Transform(l,false));
            } else if (l.matches("^\\d+$")){
                numStack.push(FractionUtil.Transform(l,true));
            }
            else {
                if(numStack.isEmpty()){
                    num1 = new Fraction(1,0);
                }
                else {
                    num1 = numStack.pop();
                }
                if(numStack.isEmpty()){
                    num2 = new Fraction(1,0);
                }
                else {
                    num2 = numStack.pop();
                }
                Fraction calculate = fourKindCalculate(num2,num1,l);
                numStack.push(calculate);
            }
        }
        return  numStack;
    }
}

ExpressionImpl

  • List createExpression(Integer n, Integer r)
    运算数和运算符是随机生成的,每一次循环生成一个运算数和一个运算符,limit控制循环的次数,即运算符的数目,在这个方法中对不合法和重复的式子进行剔除,不合法的情况有分母为零,出现负数。在调用Calculate方法计算的时候,把含有以上两种情况的式子都会剔除。
点击查看代码

public class ExpressionImpl implements Expression {

    /**
     * 生成的表达式集合
     */
    ArrayList<String> expressionList = new ArrayList<>();
    /**
     * 写入文件的表达式的集合
     */
    ArrayList<String> expressionListToFile = new ArrayList<>();

    @Override
    public List<String> createExpression(Integer n, Integer r) {
        String[] operate = new String[]{"+", "-", "*", "/"};
        Random random = new Random();
        //随机产生的运算符个数
        int count;
        StringBuilder temp;
        String tempToFile;
        for (int i = 0; i < n; i++) {
            //[1,4)
            temp = new StringBuilder();
            count = random.nextInt(3) + 1;
            for (int j = 1; j <= count; j++) {
                Fraction fraction = new Fraction(random.nextInt(n), random.nextInt(n));
                String opt = operate[random.nextInt(4)];
                // 这里要判断是否分母可能为0,如果为0则按1来计算,分数类分母默认为1
                temp.append(fraction.transferFraction()).append(" ").append(opt).append(" ");
            }
            int x2 = random.nextInt(n);
            temp.append(x2);
            tempToFile = i+1+"、"+temp + "=";
            expressionList.add(temp.toString());
            expressionListToFile.add(tempToFile);
        }
        return expressionList;
    }

    @Override
    public void writeToFile(String fileName) {
        FileUtil.writeToFile(expressionListToFile,fileName);
    }

    @Override
    public void answerToFile(List<String> answerList, String fileName) {
        FileUtil.writeToFile(answerList,fileName);
    }
}

表达式结果校验与输入输出

Check

其对应的实现类CheckImpl,使用FileUtil将答题情况写入Grade文件,调用Calculate计算对应结果并且校验,使用linkedHashMap存放题目和答案。

点击查看代码
 /**
     * 读取题目和答案文件
     *
     * @param answerFile    答案文件
     * @return exercises集合
     */
    Map<String,String> readFile(File answerFile) throws Exception;

    /**
     * 对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
     *
     */
    Result checkAnswer(Map<String,String> map) throws IOException;

    /**
     * 对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
     */
    Result checkPaper(String answerFile);

测试

生成表达式

public void testCreateExpression() {
        List<String> expression = this.expression.createExpression(5, 5);
        for (String s : expression) {
            System.out.println(s);
        }
    }

存入txt文本

问题:

答案:

一万道题目生成及其性能分析

 Expression expression = new ExpressionImpl();
        Calculate calculate = new CalculateImpl();
        String answerTemp;
        List<String> expression1 = expression.createExpression(10000, 10);
        List<String> answerList = new ArrayList<>();
        expression.writeToFile("Exercises.txt");
        for (String s : expression1) {
            String result = calculate.calculate(s);
            answerTemp = s + " =" + result;
            System.out.println(answerTemp);
            answerList.add(answerTemp);
        }
        expression.answerToFile(answerList, "Answers.txt");
    }




答案校验

题目:

答案:

其中修改了前三题的答案,所以grade输出应该是前三题错误,其余正确
校验:

七、总结
收获:
1 锻炼了团队协作和沟通的能力,学会倾听和尊重队友的意见,并在最终的决策过程达成共识。
2 为完成项目需求,需要分析具体的实现方式来解决遇到的难题,学习新的思维模式,采用相关技术克服难题,在这个过程锻炼了逻辑思维,提升了编程能力
3 结对编程体现了合作的关键性,在项目经过需求分析和详细设计后,要进入真正实现的过程,对于程序的推进,需要明确各自的目标,尽可能达到最优的实现方案,完成负责的模块,提高开发效率
不足的地方:
用Java来实现数据结构的效率不够高,没有图形化界面

posted @ 2023-09-26 11:33  无火余灰  阅读(76)  评论(0)    收藏  举报