个人作业1——四则运算题目生成程序(基于控制台)

Posted - Coding Never Ends -

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了个人作业1——四则运算题目生成程序(基于控制台)相关的知识,希望对你有一定的参考价值。

 
一、题目描述:
       实践能力的提高当然就是得多动手了,那么就从第一个个人项目开始吧,用一周的时间完成一个基于控制台的四则运算程序,实现一个自动生成小学四则运算题目的命令行程序。
从《构建之法》第一章的 “程序” 例子出发,像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”,满足以下需求:
 
1. 使用 -n 参数控制生成题目的个数,例如
       Myapp.exe -n 10 -o Exercise.txt
将生成10个题目。
2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 
      Myapp.exe -r 10
 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数
4. 每道题目中出现的运算符个数不超过3个。
5. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
     1. 四则运算题目1
     2. 四则运算题目2
          ……
 
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
6. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
    1. 答案1
    2. 答案2
 
    特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
7. 程序应能支持一万道题目的生成。
8. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,并会输出所有题目中重复的题目,输入参数如下:
     Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt -o Grade.txt
 
统计结果输出到文件Grade.txt,格式如下:
 
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
Repeat:2
RepeatDetail:
(1)   2,45+32  Repeat 3,32+45                    
(2)   5,3+(2+1)  Repeat 7,1+2+3
 
解释:
Correct: 5 ----5道题目正确,正确的题号 1,3,5,7,9
Wrong:5 -----5道题目错误,错误的题号 2,4,6,8,10
Repeat:2   2---组题目重复
(1) 第一组 题号2,题目 45+32  与题号3的题目重复,题号3为 32+45
(2)第二组  题号5,题目 3+(2+1) 与题号7的题目重复,题号7为 1+2+3
 
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
二、个人软件过程:
 
  • PSP2.1 Personal Software Process Stages Time Senior Student Time  
    Planning 计划 360 420  
    · Estimate 估计这个任务需要多少时间 360 420  
    Development 开发 200 340  
    · Analysis 需求分析 (包括学习新技术) 20 40  
    · Design Spec 生成设计文档 15 25  
    · Design Review 设计复审 10 15  
    · Coding Standard 代码规范 5 5  
    · Design 具体设计 30 60  
    · Coding 具体编码 60 120  
    · Code Review 代码复审 40 60  
    · Test 测试(自我测试,修改代码,提交修改) 20 20  
    Reporting 报告 40 30  
    · 测试报告 20 15  
    · 计算工作量 30 10  
    · 并提出过程改进计划 70 25  
             
三、代码提交
        本次实验代码已经上传至coding.net
        地址:https://git.coding.net/Lauv_/EXP1.git
四、博文规范
  • a.需求分析:
  • 本次实验题目为四则运算,题目要求设计一个简单的四则运算生成程序,既可以根据输入的值产生一定数量的题目,还可以限定题目的范围。
  • 因为针对的人群比较明确,所以涉及程序的时候应该对功能描述得清晰明了,能够使使用者一目了然。
  • 保证程序运算逻辑正确是基本的要求,其次应该要做出优化,例如分数的显示,题目难度的设计等。
  • b.功能设计:
  • 基本功能:随机生成要求内的四则运算式子,包括算式的正确答案。
  • 扩展功能:能够实时根据用户的输入判断正误,并给出正确答案,以及某次测试的正确率等表现。
  • 高级功能:可以设置题目的难度。
  • c.设计实现
  • 项目包含四个主要Java类如下
  •  

  • d.代码说明:
  • 1.RandomMath类
  • 主要用来生成随机的四则运算表达式
  •  1 package com.uml.arithmetic;
     2 
     3 import java.util.Random;
     4 
     5 public class RandomMath {
     6 
     7     private static String[] operate = new String[] { "+", "-", "×", "÷" }; // 运算符
     8     static Random r = new Random();
     9 
    10     static String RandomMath(int nums) {
    11         nums(nums);
    12             /*
    13              * 括号的位置 
    14              * 1. (a - b) - c - d = ? 
    15              * 2. (a - b - c) - d = ? 
    16              * 3. a - (b - c) - d = ? 
    17              * 4. a - (b - c - d) = ? 
    18              * 5. a - b - (c - d) = ?
    19              * 其中“-”代表运算符
    20              */
    21             String op = operate[r.nextInt(3)];// 防止出现假分数设置第二位运算符不能为“÷”
    22             String tmp1[] = getOnce_new(nums);// 第一个式子
    23             String tmp2[] = getOnce_new(nums);// 第二个式子
    24             String quesStr = "";
    25 
    26             int temp = r.nextInt(5);
    27             switch (temp) { // 括号的位置
    28             case 0:
    29                 quesStr = "(" + arraytoString(tmp1) + ")" + op + arraytoString(tmp2) + "=?";
    30                 break;
    31             case 1:
    32                 quesStr = "(" + arraytoString(tmp1) + op + tmp2[0] + ")" + tmp2[1] + tmp2[2] + "=?";
    33                 break;
    34             case 2:
    35                 quesStr = tmp1[0] + tmp1[1] + "(" + tmp1[2] + op + tmp2[0] + ")" + tmp2[1] + tmp2[2] + "=?";
    36                 break;
    37             case 3:
    38                 quesStr = tmp1[0] + tmp1[1] + "(" + tmp1[2] + op + arraytoString(tmp2) + ")" + "=?";
    39                 break;
    40             case 4:
    41                 quesStr = arraytoString(tmp1) + op + "(" + arraytoString(tmp2) + ")" + "=?";
    42                 break;
    43             case 5:
    44                 quesStr = arraytoString(tmp1) + op + arraytoString(tmp2) + "=?";
    45                 break;
    46             }
    47 
    48             return quesStr;
    49         //}
    50     }
    51 
    52     /*产生一组两目运算式*/
    53     static String[] getOnce_new(int nums) { 
    54         // String temp = "";
    55         //System.out.println("MaxNum1: "+nums);
    56         int[] numbers = nums(nums); // 操作数
    57         String temp[] = new String[3];
    58         int num = numbers[r.nextInt(nums)];
    59         int num2 = numbers[r.nextInt(nums)];
    60         String op = operate[r.nextInt(4)];
    61         if (op == "÷") { // 除法运算保证结果是真分数
    62             if (num >= 2)
    63                 num2 += num;
    64         }
    65         temp[0] = num + "";
    66         temp[1] = op;
    67         temp[2] = num2 + "";
    68 
    69         return temp;
    70     }
    71     
    72     /*字符串数组转字符串*/
    73     static String arraytoString(String str[]){
    74         if(str == null)
    75             return "";
    76         StringBuilder builder = new StringBuilder();
    77         for(int i=0; i<str.length; i++){
    78             builder.append(str[i]);
    79         }
    80         return builder.toString();
    81     }
    82     static int[] nums(int nums){
    83         int[] numbers = new int[nums]; // 操作数
    84         for (int i = 1; i <= nums; i++) {
    85             numbers[i - 1] = i;
    86         }
    87         return numbers;
    88     }
    89 }

     

  • 2. SolveQuestion类
  • 主要用来将中缀表达式转换为后缀表达式,然后进行求值
  • package com.uml.arithmetic;
    
    import java.text.DecimalFormat;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import org.junit.Test;
    
    public class SolveQuestion {
        /**
         * 提前将 符号的优先级定义好
         */
        private static final Map<Character, Integer> basic = new HashMap<Character, Integer>();
        static {
            basic.put(\'-\', 1);
            basic.put(\'+\', 1);
            basic.put(\'×\', 2);
            basic.put(\'÷\', 2);
            basic.put(\'(\', 0);// 在运算中 ()的优先级最高,但是此处因程序中需要 故设置为0
        }
    
        static String solve(String str) {
            str = str.substring(0, str.length() - 2);
            String ques = toSuffix(str);
            return dealEquation(ques);
    
        }
    
        /**
         * 将 中缀表达式 转化为 后缀表达式
         */
        static String toSuffix(String infix) {
    
            List<String> queue = new ArrayList<String>(); // 定义队列 用于存储 数字 以及最后的
                                                            // 后缀表达式
            List<Character> stack = new ArrayList<Character>(); // 定义栈 用于存储 运算符
                                                                // 最后stack中会被 弹空
            try {
                char[] charArr = infix.trim().toCharArray(); // 字符数组 用于拆分数字或符号
                String standard = "×÷+-()"; // 判定标准 将表达式中会出现的运算符写出来
                char ch = \'&\'; // 在循环中用来保存 字符数组的当前循环变量的 这里仅仅是初始化一个值 没有意义
                int len = 0; // 用于记录字符长度 【例如100*2,则记录的len为3 到时候截取字符串的前三位就是数字】
                for (int i = 0; i < charArr.length; i++) { // 开始迭代
    
                    ch = charArr[i]; // 保存当前迭代变量
                    if (Character.isDigit(ch)) { // 如果当前变量为 数字
                        len++;
                    } else if (Character.isLetter(ch)) { // 如果当前变量为 字母
                        len++;
                    } else if (ch == \'.\') { // 如果当前变量为 . 会出现在小数里面
                        len++;
                    } else if (Character.isSpaceChar(ch)) { // 如果当前变量为 空格
                                                            // 支持表达式中有空格出现
                        if (len > 0) { // 若为空格 代表 一段结束 ,就可以往队列中 存入了 【例如100 * 2
                                        // 100后面有空格
                                        // 就可以将空格之前的存入队列了】
                            queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i))); //// 队列存入
                                                                                                // 截取的
                                                                                                // 字符串
                            len = 0; // 长度置空
                        }
                        continue; // 如果空格出现,则一段结束 跳出本次循环
                    } else if (standard.indexOf(ch) != -1) { // 如果是上面标准中的 任意一个符号
                        if (len > 0) { // 长度也有
                            queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i))); // 说明符号之前的可以截取下来做数字
                            len = 0; // 长度置空
                        }
                        if (ch == \'(\') { // 如果是左括号
                            stack.add(ch); // 将左括号 放入栈中
                            continue; // 跳出本次循环 继续找下一个位置
                        }
                        if (!stack.isEmpty()) { // 如果栈不为empty
                            int size = stack.size() - 1; // 获取栈的大小-1 即代表栈最后一个元素的下标
                            boolean flag = false; // 设置标志位
                            while (size >= 0 && ch == \')\' && stack.get(size) != \'(\') { // 若当前ch为右括号,则
                                                                                        // 栈里元素从栈顶一直弹出,直到弹出到
                                                                                        // 左括号
                                queue.add(String.valueOf(stack.remove(size))); // 注意此处条件:ch并未入栈,所以并未插入队列中;同样直到找到左括号的时候,循环结束了,所以左括号也不会放入队列中【也就是:后缀表达式中不会出现括号】
                                size--; // size-- 保证下标永远在栈最后一个元素【栈中概念:指针永远指在栈顶元素】
                                flag = true; // 设置标志位为true 表明一直在取()中的元素
                            }
                            while (size >= 0 && !flag && basic.get(stack.get(size)) >= basic.get(ch)) { // 若取得不是()内的元素,并且当前栈顶元素的优先级>=对比元素
                                                                                                        // 那就出栈插入队列
                                queue.add(String.valueOf(stack.remove(size))); // 同样
                                                                                // 此处也是remove()方法,既能得到要获取的元素,也能将栈中元素移除掉
                                size--;
                            }
                        }
                        if (ch != \')\') { // 若当前元素不是右括号
                            stack.add(ch); // 就要保证这个符号 入栈
                        } else { // 否则就要出栈 栈内符号
                            stack.remove(stack.size() - 1);
                        }
                    }
                    if (i == charArr.length - 1) { // 如果已经走到了 中缀表达式的最后一位
                        if (len > 0) { // 如果len>0 就截取数字
                            queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len + 1, i + 1)));
                        }
                        int size = stack.size() - 1; // size表示栈内最后一个元素下标
                        while (size >= 0) { // 一直将栈内 符号全部出栈 并且加入队列中
                                            // 【最终的后缀表达式是存放在队列中的,而栈内最后会被弹空】
                            queue.add(String.valueOf(stack.remove(size)));
                            size--;
                        }
                    }
    
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            return queue.stream().collect(Collectors.joining(",")); // 将队列中元素以,分割
                                                                    // 返回字符串
        }
    
        /**
         * 将 后缀表达式 进行 运算 计算出结果
         * 
         * @param equation
         * @return
         */
        public static String dealEquation(String equation) {
            String[] arr = equation.split(","); // 根据, 拆分字符串
            List<String> list = new ArrayList<String>(); // 用于计算时
                                                            // 存储运算过程的集合【例如list中当前放置
                                                            // 100 20 5 / 则取出20/5
                                                            // 最终将结果4存入list
                                                            // 此时list中结果为 100 4 】
    
            try {
                for (int i = 0; i < arr.length; i++) { // 此处就是上面说的运算过程,
                                                        // 因为list.remove的缘故,所以取出最后一个数个最后两个数
                                                        // 都是size-2
                    int size = list.size();
                    switch (arr[i]) {
                    case "+":
                        double a = Double.parseDouble(list.remove(size - 2)) + Double.parseDouble(list.remove(size - 2));
                        list.add(String.valueOf(a));
                        break;
                    case "-":
                        double b = Double.parseDouble(list.remove(size - 2)) - Double.parseDouble(list.remove(size - 2));
                        list.add(String.valueOf(b));
                        break;
                    case "×":
                        double c = Double.parseDouble(list.remove(size - 2)) * Double.parseDouble(list.remove(size - 2));
                        list.add(String.valueOf(c));
                        break;
                    case "÷":
                        double d = Double.parseDouble(list.remove(size - 2)) / Double.parseDouble(list.remove(size - 2));
                        list.add(String.valueOf(d));
                        break;
                    default:
                        list.add(arr[i]);
                        break; // 如果是数字 直接放进list中
                    }
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
    
            DecimalFormat df = new DecimalFormat("0.00"); // 设置double类型小数点后位数格式
            // df.format(Double.parseDouble(list.get(0))).toString();
            return list.size() == 1 ? df.format(Double.parseDouble(list.get(0))).toString() : "运算失败"; // 最终list中仅有一个结果,否则就是算错了
        }
    }

     

  • e.测试运行

五、小结

本次实验做的不是很理想,只实现了随机生成四则运算以及计算求值的功能,题目查重,分数的转换等功能都没有完全实现。

但是关于解题给了我一个新思路,将普通的中缀表达式转换为后缀表达式,然后计算求值。这样就不用考虑括号的问题,能够节省很多步骤。

还是要多努力,寻找一些先进的算法,常使用二叉树来存储算式。

以上是关于个人作业1——四则运算题目生成程序(基于控制台)的主要内容,如果未能解决你的问题,请参考以下文章

个人作业1——四则运算题目生成程序(基于控制台)

个人作业1——四则运算题目生成程序(基于控制台)

个人作业1——四则运算题目生成程序(基于控制台)

个人作业1——四则运算题目生成程序(基于控制台)

个人作业1——四则运算题目生成程序(基于控制台)

个人作业1——四则运算题目生成程序(基于控制台)