四则运算题目生成系统

GitHub地址:https://github.com/soperfect/SE_work3_arithmetic

结对编程小伙伴:古梓欣 学号:3118004991

结对编程方式:远程计算机控制

一、项目相关要求

  1. 使用 -n 参数控制生成题目的个数,例如Myapp.exe -n 10,将生成10个题目

  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如Myapp.exe -r 10,将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。

  3. 生成的题目中计算过程不能产生负数

  4. 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。

  5. 每道题目中出现的运算符个数不超过3个。

  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目,即运算的过程不能一样。

  7. 生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下

    1. 四则运算题目1

    2. 四则运算题目2

  8. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:

    1. 答案1

    2. 答案2

  9. 程序应能支持一万道题目的生成。

  10. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:Myapp.exe -e <exercisefile>.txt -a <answerfile>.txtMyapp.exe -e <exercisefile>.txt -a <answerfile>.txt

    Correct: 5 (1, 3, 5, 7, 9)

    Wrong: 5 (2, 4, 6, 8, 10)

    其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

二、解题思路

  • 把分数和整数合并一起表示,整数为分母为1的分数,方便分数的计算

  • 运算符不超过3个,则最多的运算数有4个,分多种情况讨论,1个运算符下有分数的和没分数的,简化需要讨论的情况,不对哪个运算的数讨论是否是分数,只讨论式子中有几个分数。

  • 重复的式子也可看成运算顺序和参与运算的数值相同的式子,记录下每则式子的运算过程,与新生成的式子的运算过程进行对比,便可做到去重

三、设计实现过程

  1. 将整个项目分为三个模块,mainentityservicemodel函数中为实体类,用于表示分数的类,用于表示运算式子和运算结果的类,service模块中主要是本项目所需要的一些功能函数,随机生成题目的类,检查题目是否重复的类,检查用户输入的结果和答案有哪些异同的类,main模块中有main函数,调用service模块中的函数实现本项目的功能。

四、代码说明

用于存放算式及算式结果的类

/**
 * 用于存放算术表达式以及结果
 * @author Red Date.
 * @date 2020/3/27 23:17
 */
public class ResultMap {
​
    //表达式
    private String expression;
    //结果
    private String result;
​
    public String getExpression() {
        return expression;
    }
​
    public void setExpression(String expression) {
        this.expression = expression;
    }
​
    public String getResult() {
        return result;
    }
​
    public void setResult(String result) {
        this.result = result;
    }
​
    public ResultMap(String expression, String result) {
        this.expression = expression;
        this.result = result;
    }
​
    public ResultMap(){
​
    }
}

用于表示数的实体类

/**
 * 分数类,用于表示整数和分数
 * @author Red Date.
 * @date 2020/3/28 22:14
 */
public class Fraction {
​
    private int numerator;      //分子
    private int denominator;    //分母
public Fraction(int numerator,int denominator){
        this.numerator = numerator;
        this.denominator = denominator;
    }
​
    public Fraction(int numerator){
        this.denominator = 1;
        this.numerator = numerator;
    }
​
    //生成一个分数或整数,在给定的范围内
    public Fraction(boolean isFraction,int bound){
        Random random = new Random();
        int numerator = random.nextInt(bound);
        while (numerator==0 ){
            numerator = random.nextInt(bound);
        }
        //生成一个分数
        if(isFraction){
            int denominator = random.nextInt(bound);
            //分母不能为零,分子也不能为零
            while (denominator==0 ){
                denominator = random.nextInt(bound);
            }
            this.numerator = numerator;
            this.denominator = denominator;
        }else{
            this.numerator = numerator;
            this.denominator = 1;
        }
    }
​
    public Fraction(){
​
    }
​
    //加法运算
    public Fraction addition(Fraction fraction){
        int numerator = fraction.getNumerator();
        int denominator = fraction.getDenominator();
        //新的分子
        int newNumerator = this.numerator * denominator + this.denominator * numerator;
        //新的分母
        int newDenominator = this.denominator * denominator;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }
​
    //减法运算
    public Fraction subtraction(Fraction fraction){
        int numerator = fraction.getNumerator();
        int denominator = fraction.getDenominator();
        //新的分子
        int newNumerator = this.numerator * denominator - this.denominator * numerator;
        //新的分母
        int newDenominator = this.denominator * denominator;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }
​
    //除法运算
    public Fraction division(Fraction fraction){
        int numerator = fraction.getNumerator();
        int denominator = fraction.getDenominator();
        //新的分子
        int newNumerator = this.numerator * denominator ;
        //新的分母
        int newDenominator = this.denominator * numerator;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }
​
    public Fraction multiplication(Fraction fraction){
        int numerator = fraction.getNumerator();
        int denominator = fraction.getDenominator();
        //新的分子
        int newNumerator = this.numerator * numerator ;
        //新的分母
        int newDenominator = this.denominator * denominator;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }
​
    // 用辗转相除法求最大公约数
    private static int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
​
    // 对分数进行约分
    public void appointment() {
        // 如果分子是0或分母是1就不用约分了
        if (numerator == 0 || denominator == 1)
            return;
        int gcd = gcd(numerator, denominator);
        this.numerator /= gcd;
        this.denominator /= gcd;
    }
​
    //判断该数是否为负数
    public boolean isNegative(){
        return  (this.getDenominator()<0 || this.getNumerator()<0);
    }
    
    //重写toString,返回约分完的结果
    @Override
    public String toString(){
        appointment();
        //分子为0
        if(numerator == 0)
            return ""+ numerator;
        //真分数
        else if(numerator > denominator){
            if(numerator % denominator == 0)
                return "" + numerator/denominator;
            return "" + numerator/denominator + "'" + numerator%denominator +"/" + denominator;
        }else if(numerator == denominator){
            return "" + numerator;
        }
            return "" + numerator + "/" + denominator;
    }
​
    public int getNumerator() {
        return numerator;
    }
​
    public void setNumerator(int numerator) {
        this.numerator = numerator;
    }
​
    public int getDenominator() {
        return denominator;
    }
​
    public void setDenominator(int denominator) {
        this.denominator = denominator;
    }
}

用于计算结果

    //采用递归的方式计算结果
    public static Fraction calculate(List list){
​
        int place = whetherMulDiv(list);
        String flag = null;
        if(place != -1){
            flag = addSubMulDiv(list,place,1);
​
        }else {
             flag = addSub(list);
        }
        if(flag.equals("error")){
            return null;
        }
        if(list.size() == 1)
            return (Fraction)list.get(0);
        return calculate(list);
    }
​
    //将运算顺序添加到集合中
    public static List<String> operationsOrder(List list){
        List<String> result = new ArrayList<String>();
        String flag = null;
        while (list.size()!=1){
            int place = whetherMulDiv(list);
            if(place != -1){
                result.add(list.get(place-1).toString());
                result.add((String) list.get(place));
                result.add(list.get(place+1).toString());
                flag = addSubMulDiv(list,place,1);
            }else {
                int place1 = 0;
                for (int i=0;i<list.size();i++){
                    if(list.get(i).equals("+")||list.get(i).equals("-")){
                        place1 = i;
                        break;
                    }
​
                }
                result.add(list.get(place1-1).toString());
                result.add((String) list.get(place1));
                result.add(list.get(place1+1).toString());
                flag = addSubMulDiv(list,place1,0);
            }
        }
        if("error".equals(flag))
            return null;
        return result;
    }
​
    //判断集合中是否有乘除,返回乘除的位置
    private static int whetherMulDiv(List list){
        for (int i=0;i<list.size();i++){
            if(list.get(i).equals("*")||list.get(i).equals("÷"))
                return i;
        }
        return -1;
    }
​
    //一次加法减法或乘法除法运算,0为加减,1为乘除
    private static String addSubMulDiv(List list , int place,int flag){
​
        String operator1 = null;
        String operator2 = null;
        if (flag == 0){ //实现加法或者减法
            operator1 = "+";
            operator2 = "-";
        }else if(flag == 1){
            operator1 = "*";
            operator2 = "÷";
        }
        //获取运算前后的数
        Fraction fraction1 = (Fraction) list.get(place-1);
        Fraction fraction2 = (Fraction) list.get(place+1);
        String symbol = (String) list.remove(place);
        //移除后一个数
        list.remove(place);
        if(symbol.equals(operator1)){
            Fraction result = new Fraction();
            if(operator1.equals("*")){
                result = fraction1.multiplication(fraction2);
            }else
                result  = fraction1.addition(fraction2);
            if(result.isNegative()){
                return "error";
            }
            list.set(place-1,result);
        }else if(symbol.equals(operator2)){
            Fraction result = new Fraction();
            if(operator2.equals("÷")){
                result = fraction1.division(fraction2);
            }else
                result = fraction1.subtraction(fraction2);
            if(result.isNegative()){
                return "error";
            }
            list.set(place-1,result);
        }
        return "right";
    }
​
    //加法和减法运算,直接算出结果
    private static String addSub(List list){
​
        for (int i =0;i<list.size();i++){
            if(list.get(i).equals("+")){
                Fraction fraction1 = (Fraction)list.get(i-1);
                list.remove(i);
                Fraction fraction2 = (Fraction)list.get(i);
                list.remove(i);
                Fraction result =  fraction1.addition(fraction2);
                if(result.isNegative()){
                    return "error";
                }
                list.set(i-1,result);
                i--;
            }else if(list.get(i).equals("-")){
                Fraction fraction1 = (Fraction)list.get(i-1);
                list.remove(i);
                Fraction fraction2 = (Fraction)list.get(i);
                list.remove(i);
                Fraction result =  fraction1.subtraction(fraction2);
                if(result.isNegative()){
                    return "error";
                }
                list.set(i-1,result);
                i--;
            }
        }
        return "right";
    }

检查是否重复

    /**
     * 判断是否重复,不重复则将表达式加入已有集合中
     *
     * @param list           要判断的运算式拆分后的list集合
     * @param expressionList 已经生成的运算式的集合
     * @return boolean,true为重复
     */
    public static boolean isRepeat(List<String> list, List<List<String>> expressionList) {
        for (List<String> expression : expressionList) {
            if (expression.size() == list.size()) {
                int length = list.size();
                int num = -2;    //用于累计符合条件的运算符
                int index1 = 0;
                //运算符和数字一致,判断运算顺序是否一致
                for (int index = 1; index < length; index += 3) {
                    if (expression.get(index).equals(list.get(index))) {
                        //运算符相同时,前后数字是否一致
                        if ((expression.get(index - 1).equals(list.get(index - 1))
                                && expression.get(index + 1).equals(list.get(index + 1)))
                                || (expression.get(index - 1).equals(list.get(index + 1))
                                && expression.get(index + 1).equals(list.get(index - 1)))) {
                            num += 3;
                        }
​
                    }
                    index1 = index;
                }
                if (num == index1) {
                    return true;
                } else {
                    expressionList.add(list);
                    return false;
                }
            }
        }
        expressionList.add(list);
        return false;
    }

检查答案

/**
     * 检查答案是否正确
     * @param checkFile 自己填的答案的文件名
     * @param answersFile 标准答案文件名
     * @throws IOException
     */
    public static void check(String checkFile ,String answersFile) throws IOException {
        BufferedReader readerCheck = new BufferedReader(new InputStreamReader(new FileInputStream(checkFile)));
        BufferedReader answerCheck = new BufferedReader(new InputStreamReader(new FileInputStream(answersFile)));
        String checkLine ="";
        String answerLine ="";
        String correct = "";
        String wrong = "";
        int correctNum = 0;
        int wrongNum = 0;
        while ((checkLine = readerCheck.readLine()) != null && (answerLine = answerCheck.readLine()) != null){
            String[] checkString = checkLine.split("\\.");
            String[] answerString = answerLine.split("\\.");
            if(checkString[1].equals(answerString[1])){
                if (correct.equals("")){
                    correct = answerString[0];
                    correctNum++;
                }
                else{
                    correct = correct+","+answerString[0];
                    correctNum++;
                }
            }else{
                if (wrong.equals("")){
                    wrong = answerString[0];
                    wrongNum++;
                }
                else{
                    wrong = wrong+","+answerString[0];
                    wrongNum++;
                }
            }
        }
        readerCheck.close();
        answerCheck.close();
        OutputStreamWriter resultWriter = new OutputStreamWriter(new FileOutputStream(new File("Grade.txt")), "UTF-8");
        resultWriter.append("Correct:"+correctNum+"("+correct+")");
        resultWriter.append("\n");
        resultWriter.append("Wrong:"+wrongNum+"("+wrong+")");
        resultWriter.close();
        System.out.println("Correct:"+correctNum+"("+correct+")");
        System.out.println("Wrong:"+wrongNum+"("+wrong+")");
    }

五、测试运行

1、生成10000道题目

2、检查答案,圈出来的地方的答案专门改成错误的

3、参数错误测试

4、代码覆盖率

mark

 

六、PSP

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

七、项目小结

  1. 吸取了上一个作业个人项目的经验,这次在动手写代码之前,先仔细的设计了各个类和函数,捋清楚各个类之间的关系之后再开始着手写代码,避免了在实现功能的同时考虑各个功能的逻辑关系,提高了效率。

  2. 本次是结对编程项目,通过远程计算机控制实现两个人共用一台电脑进行编程,由于网络延迟以及线上沟通的不便性,降低了开发的效率,同时有结对编程的小伙伴相互监督,避免了一些低级错误和一些逻辑上判断的死角的发生。

  3. 本次项目的难点在于分数的表示、如何去重、如何计算出正确的结果,解决了这几个问题,剩下的东西便不会很难

posted @ 2020-04-14 20:36  RedDate  阅读(399)  评论(0编辑  收藏  举报