因为之后的几天会有很多的事情要忙,所以就突击把这次的作业完成了。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; }