因为之后的几天会有很多的事情要忙,所以就突击把这次的作业完成了。1.0版本还比较粗糙,之后如果有空的话会再做修改。

  Coding.net原码仓库地址:https://git.coding.net/ShadowGhostH/homework.git

博客更新状态

3.20  16:30 更新

  我也没想到这么快就更新了,感谢有小伙伴提出题目当中要求计算过程中不能出现负数。于是我就在计算时对‘-’操作符又做了一次特判,出现‘-‘时就计算之前表达式的值breNum,然后对减数在[1, breNum] 的范围内生成就可以了。同时需要注意,为了不使减数放大,我们要控制减号后的一个操作符不乘号,要不然会导致减数放大而出现负数

2.0 End 有空继续更新

3.25 18:00 更新

  因为天梯赛和周训的原因,一直也没再有机会来做这个项目,所以附加内容没有完成。之前为了引导同学思路留下了输入引导和各种注释,在deadline之前把输入引导去掉换成了命令行读入,last更新。

End

4.16 17:21 更新

  很开心ACM校赛拿了一等奖第一名,同时上次更新写的last更新好像也成了一个flag。拖到了deadline最后三小时才提交作业确实有些拖沓了,但是准备了这么久的校赛拿了不错的成绩还是特别开心。

  本次更新将可生成的算式个数更新至1000(数组能开多大就能生成多少个,而且采用读取的sum值动态开数组,所以原则上不存在上限(上限和能开的数组范围有关)),并将result.txt文件的生成位置更改到与src文件夹同级目录下。

3.0 End 希望是最后一次更新

需求分析

  首先明确项目需要,一个可以定做小学四则运算的程序。所以需要我们能够随机生成不同的四则运算,范围应在100以内,包含“+,-,*,÷”四种操作。要求:不存在小数,题目尽量不重复。

功能设计

  基本功能应当包括,读入用户想要定制的算式数,以及对应的运算符数量,然后生成对应数目和难度的四则运算式子。并由程序自己计算,给出相应的答案。

  扩展功能可以用:在算式中包含括号,计算存在真分数的式子。

设计实现

  因为要随机生成运算数与运算符,所以我们可以用Math.random()生成随机数来定制我们的运算数,并可以事先将运算符“+,-,*,÷”存入数组内,然后通过Math.random()方法随机访问数组下标的方式,来随机确定运算符。

  生成四则运算的式子后,要去由计算机计算这个表达式的值,因为计算机无法直观的判断表达式中各个运算的优先级关系,所以我们选择将中序表达式转化为后序表达式的方式来计算,又因为不需要输出后序表达式,所以我们可以在转化的过程中直接完成计算。

算法详解

  1.定制运算数与运算符

    /** 用于生成指定范围的随机数  **/
    static private int makeRandom(int min, int max){
        return (int)(Math.random()*(max - min) + min + 0.5);
    }

   首先在Lib类中,我选择了这样一个方法,通过Math.random()方法,生成一个 [min, max] 范围中的数,在我们之后的运算过程中,会对不同范围的随机数有不同的要求,届时会多次调用这个方法。

  2.生成随机随机的运算式

/** 一共生成sum个,ops个操作的算式 **/
    static public void makeQuestions(String[] questionList, int sum, int ops){
        /** 用于存放操作符 和 操作数  **/
        char opInQuestion[] = new char[15];
        int numInQuestion[] = new int[15];
        
        /** for循环生成sum个question **/
        for(int i=0; i<sum; i++){
            numInQuestion[0] = makeRandom(1, 100);
            /** 每个问题预生成第一个操作数,然后for循环对应生成之后的ops个操作 和对应的操作数 **/
            for(int j=0; j<ops; j++){
                /** 乘除不连续出现避免出错 **/
                if(j!=0 && getw(opInQuestion[j-1])==2)
                    opInQuestion[j] = op[makeRandom(0, 1)];
                else opInQuestion[j] = op[makeRandom(0, 3)];
                numInQuestion[j+1] = makeRandom(1, 50);
                /** 特判除法构造整除 **/
                if(opInQuestion[j] == '÷') {
                    numInQuestion[j+1] = makeRandom(1, 10);
                    numInQuestion[j] = numInQuestion[j+1]*makeRandom(2, 10);
                }
                
                /** 特判乘法控制范围 **/
                else if(opInQuestion[j] == '*') {
                    numInQuestion[j] = makeRandom(1, 20);
                    numInQuestion[j+1] = makeRandom(1, 100/numInQuestion[j]);
                }
            }
            /** 将问题拼接为String **/
            String question = "" + numInQuestion[0];
            for(int j=0; j<ops; j++)
                question = question + opInQuestion[j] + numInQuestion[j+1];
            
            //System.out.println(question);
            
            question = question + "=" + calQuestion(question);
            
            System.out.println(question);
            questionList[i] = question;
        }
    }

  makeQuestions() 方法中共有三个参数,questionList[]  用于储存生成的运算式,sum为定制运算式的个数,ops为定制运算式中操作符的个数。

  在实现的过程中,opInQuestion[] 数组用于顺序储存运算式中出现的操作符,numInQuestion[] 用于顺序储存运算式中出现的操作数。因为每个式子中至少有一个数字,所以我们可以预生成numInQuestion[0], 然后循环生成其余的操作数和操作符。

  根据题目要求不能出现小数,所以我们可以在操作符出现除法操作时,特判除号前后的两个数字,并构造他们让他们成为倍数关系。即,先随机生成除数,然后随机生成倍数,通过除数和倍数来构造被除数。

  最后将所有的操作数和操作符拼接成一个字符串存入questionList[] 中,问题生成完成。

  以上是我最开始的想法,后来在调试过程中我发现自己的思路是存在漏洞的:因为每次会定制除号前后的两个数,所以当出现连续除法操作时,第一个除数会被覆盖从而第一个除法运算将不满足整除。而如果我不定制被除数的话,又不能保证被除数是不是素数(除1这个操作实在是太蠢了),所以我偷懒选择了不让除法连续出现。而同时考虑到连续乘法可能会让数据过大不适合小学生计算,所以有对乘法进行了特判来调整最后结果可能的大小不至于太大。

  3.计算表达式的值

  

/** 将question转化为后缀表达式并求值   **/
    static private int calQuestion(String question){
        char[] ch = new char[50];
        char[] oc = new char[15]; 
        int[] num = new int[15];
        int temp = 0, pn = -1, pc = -1;  //temp用于存放中间数值,pn用于在num数组中模拟栈顶, pc用于在ch数组中模拟栈顶
        
        ch = question.toCharArray();
        
        for(int i=0; i<ch.length; i++){
            if(Character.isDigit(ch[i])) {
                temp = temp*10 + ch[i]-'0'; 
                if(i == ch.length-1)
                    num[++pn] = temp;   //最后一个数入栈
            }
            else {
                num[++pn] = temp;       //temp入栈
                temp = 0;
                
                while(pc!=-1 && getw(oc[pc]) >= getw(ch[i]) ) {
                    int num1 = num[pn--];
                    int num2 = num[pn--];
                    char ch1 = oc[pc--];
                    num[++pn] = calc(num2, num1, ch1);
                }
                
                //if(pc == -1) oc[++pc] = ch[i];
                oc[++pc] = ch[i];
            }
        }

  中序表达式转后序表达式算是比较基础的栈的应用,考虑到项目并不需要输出后序表达式,所以在转后序表达式的过程中直接完成计算操作。又因为JAVA不像C++有stl容器(也可能是因为我菜所以不知道), 所以选择了用数组模拟栈的方法。

  顺序读入字符串中的每个字符,如果是数字就计算其代表的值,如果是操作符就将计算的数字入栈(数字栈 num[]), 然后如果字符栈(oc[] )为空,就将操作符入栈。如果不为空,则将字符栈中优先级大于等于当前操作符的字符弹出并计算。当扫完整个数组后,将最后一个操作数入栈,并顺序计算字符栈中的每个操作符,最后数字栈顶的元素即为运算表达式的结果。

  其中调用了两个方法,分别完成一步运作操作和判断运算符优先级,代码如下。

    /** 完成一步运算 **/
        static private int getw(char c){
        if(c=='*' || c=='÷') return 2;
        else if(c=='+' || c=='-') return 1;
        else return -1;
    }
    
        /** 判断运算优先级 **/
    static private int calc(int a, int b, char c) {
        
        //System.out.println("" + a +c + b);
        
        if(c == '+') return a + b;
        else if(c == '-') return a - b;
        else if(c == '*') return a * b;
        else if(c == '÷') return a / b;
        else return -1;
    }
View Code

相关文章:

  • 2021-12-14
  • 2021-12-23
  • 2021-06-09
  • 2021-09-12
  • 2021-08-23
猜你喜欢
  • 2021-05-23
相关资源
相似解决方案