Coding仓库地址:https://git.coding.net/liuc144/2016012040homework.git
一、需求分析
1、脚本需要通过JAVA命令行运行。
2、参数大小在1-1000之间,并且不允许非数字参数。
3、每道练习题里至少有两道运算符,最多不能大于五道。
4、练习题在运算过程中不能出现负数或者小数。
5、将学号与生成的n道题练习题及其正确的答案输出到文件“result.txt”里,不能生成额外信息。
6、由于算术题是出给小学生的,算术题的运算结果最好不要大于1000。
7、算术题最好不要重复。
8、程序可以重复利用,便于测试。
9、小学生的算术题也要有难度梯度,要分为简单题和困难题。
二、功能设计
如图所示,四则运算项目包含三大模块。
1、生成算术题:通过public String[] getCaculate(int n,String pattern){....};方法来获得随机算术题,算术题是依靠随机运算符数组和随机数字通过String类型的+来生成的。它分两种模式,一是只有加减运算符,的EASY模式和四则运算符都存在的HARD模式.此番设计是为了照顾水平不高的学生。
2、中缀表达式转换后缀表达式:为什么要进行转换呢?我并没有看多少老师给的调度场算法,因为我觉得我的方法也很简单。算表达式的值仅仅只用转换式子,再对后缀表达式求值就行
原表达式即中缀表达式是人最习以为常、是我们最容易接受的形式。如中缀表达式:
我们很容易就能理解表达式的数学含义,但是要把表达式丢给计算机去处理,它并不能像人一样有逻辑的去判断先处理哪一步,后处理哪一步,它只会严格的按照从左只有执行,因此为了符合计算机运行方式,必须把原表达式转换为对应的后缀表达式才行。
我的设计思路很简单,解决这个项目只需要三个模块。这三个模块是逐级调用的关系,Main模块调用Caculate模块,Caculate模块调用ToSuffix模块。
他们的逻辑关系:
1、Main模块,当老师运行脚本输入命令时,用来判断输入是否非法,并根据不同的非法信息给予提示。
其精华是
/**
* 利用正则表达式判断输入是否合法
*/
result = args[0].matches("[0-9]+");
/**
*用来判断是否存在pattern的命令(hard模式或者easy模式,默认状态下是hard模式)
*/
if(args.length==1)
arr = test.getCaculate(n,"hard");
else
arr=test.getCaculate(n, args[1]);
最后输出到result.txt文件
2、Caculate模块,主要功能就是生成随机表达式,然后调用ToSuffix模块求值判断是否非法。
这里简单的说一下我是如何设计这个模块的。
方法参数是n,n是算术题的题数。
用了两个字符数组,一个是easy模式会包含的操作符,一个是hard模式会包含的操作符。
for循环,每一次循环都会生成一个符合题意的表达式,它包括两个小模块。
3、ToSuffix模块
它分为两个小模块,一个是toSuffix(String index)用来将中缀表达式转化为后缀表达式,另一个是后缀表达式求值。两个算法解决问题,详细的算法在后面解释
四、算法详解
1、 生成表达式
首先定义字符数组
private final char[] operator = { '+', '-', '*', '/' };
for循环,每一次循环都会得到一个合法的算术题
一个子for循环,rOperator变量用来操作3~5个运算符的随机性
一个while循环,用int random = (int) (Math.random() * 100);生成100以内的随机数,int r = (int) (Math.random() * 4);在字符数组中求得随机操作符(hard模式)
调用ToSuffix模块的函数计算算术题的值。double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str)));
if (result == (int) result && result >= 0 && result <= 1000)如果结果满足无负数,无小数,结果小于1000就判定合法。
将合法的式子放入arr[]数组内。
public String[] getCaculate(int n,String pattern) { String[] arr = new String[1010]; String str = null; int count = 0; for (int i = 0; i < n; i++) { // for循环,每一次循环都会得到一个合法的算术题 int rOperator; for (;;) { //一个子for循环,rOperator变量用来操作3~5个运算符的随机性 rOperator = (int) (Math.random() * 5 + 6); if (rOperator % 2 == 0) break; } while (true) { //一个while循环,用int random = (int) (Math.random() * 100);生成100以内的随机数, for (int j = 1; j <= rOperator; j++) { //int r = (int) (Math.random() * 4);在字符数组中求得随机操作符(hard模式) int random = (int) (Math.random() * 100); if (j == 1) str = random + ""; if (j % 2 == 0) str += random; else { if(pattern=="hard"){ int r = (int) (Math.random() * 4); str += operator[r]; }else{ int r = (int) (Math.random() * 2); str += Eoperator[r]; } } } /** * 验证算术题是否非法 */ ToSuffix tx = new ToSuffix(); //调用ToSuffix模块的函数计算算术题的值。double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str))); double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str))); //if (result == (int) result && result >= 0 && result <= 1000)如果结果满足无负数,无小数,结果小于1000就判定合法。 if (result == (int) result && result >= 0 && result <= 1000) { arr[count] = str + " = " + (int) result; //将合法的式子放入arr[]数组内。 count++; break; } } } return arr; } }
2、 中缀表达式转后缀表达式
算法思想
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静态代码块的作用是对映射进行初始化,用static可以节省空间和时间,提升程序效率。
也可以定义函数通过识别不同的操作符返回相应的值,'(',')'的值>'*','/'的值>'-','+'
设置一个栈,开始时,栈为空,然后从左到右扫描后缀表达式。
若遇操作数,则进栈;若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的 放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕。此时,栈中仅有一个元素,即为运算的结果。
public double suffixToArithmetic(String exp) { Stack<Double> stack=new Stack<Double>(); //使用正则表达式匹配数字 pattern=Pattern.compile("\d+||(\d+\.\d+)"); //将字符串分割成字符串数组 String strs[]=exp.split(""); for (int i = 0; i < strs.length; i++) { if(strs[i].equals("")) continue; //如果是数字,则入栈 if((pattern.matcher(strs[i])).matches()) { stack.push(Double.parseDouble(strs[i])); }else { //如果是运算符,则弹出运算符,计算结果 double b=stack.pop(); double a=stack.pop(); //将运算结果重新压入栈中 stack.push(calcute(a, b, strs[i])); } } return stack.pop(); } 根据符号计算最终结果 public static double calcute(double a,double b,Stringsimble) { if(simble.trim().equals("+")) return a+b; if(simble.trim().equals("-")) return a-b; if(simble.trim().equals("*")) return a*b; if(simble.trim().equals("/")) return a/b; return 0; }
五、测试运行
一、正常运行
二、Easy模式下生成的算术题
三、输入错误的几种情况
1、输入负数
2、输入大于1000的数
3、输入非法命令
4、输入多个命令
5、没有输入命令
六、总结
花了将近30~40个小时来完成这个作业,终于要有了尾声。面对这个项目我并没有犯怵,因为总体代码都服从我刚开始构思出的框架内。这作业难吗?不难!但为什么花了这么长时间?编码基础差,项目编写流程不熟悉。说实话,一开始写这份项目是没有头绪的。所以我从代码中寻找思路,越写代码越了解这个项目。我写了三个模块,方便我设计项目。
有了这三个模块,我就可以从大局出发,一点一点从上而下慢慢写。光是设计着三个模块和完成基本部分代码就花了编码时间的3分之一,也就7个小时,两个小时构思出模块,五个小时完成基本部分代码。后来的14个小时解决的是生成题目的算法,和表达式转换和后缀表达式求值的算法。
七、PSP
PSP |
任务内容 |
计划共完成需要的时间(h) |
实际完成需要的时间(h) |
Planning |
计划 |
1 |
0.5 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
0.5 |
1 |
Development |
开发 |
15 |
33 |
· Analysis |
· 需求分析 (包括学习新技术) |
1 |
4 |
· Design Spec |
· 生成设计文档 |
1 |
2 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
1 |
1 |
· Design |
· 具体设计 |
1 |
3 |
· Coding |
· 具体编码 |
9 |
20 |
· Code Review |
· 代码复审 |
1 |
1 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
1 |
2 |
Reporting |
报告 |
6 |
8 |
· Test Report |
· 测试报告 |
2 |
4 |
· Size Measurement |
· 计算工作量 |
1 |
2 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
3 |
2 |
很明显,预期和现实有着过大的差距,原因为何?敲代码不熟练,项目流程不熟悉。而且,我做这个项目也没有按照这个流程走。我也不太喜欢这个流程,我喜欢的流程是,自己设计框架,然后写基础代码,在写代码的时候就会注意到很多不敲打时注意不到的细节。基础铺垫好了,再去写设计文档,这样写出来的文档会高一个档次。因为你敲代码的时候会不停的对项目加深理解,理解越深,写出来的需求就越详细,功能设计就越完善。