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、中缀表达式转换后缀表达式:为什么要进行转换呢?我并没有看多少老师给的调度场算法,因为我觉得我的方法也很简单。算表达式的值仅仅只用转换式子,再对后缀表达式求值就行
原表达式即中缀表达式是人最习以为常、是我们最容易接受的形式。如中缀表达式:
我们很容易就能理解表达式的数学含义,但是要把表达式丢给计算机去处理,它并不能像人一样有逻辑的去判断先处理哪一步,后处理哪一步,它只会严格的按照从左只有执行,因此为了符合计算机运行方式,必须把原表达式转换为对应的后缀表达式才行。
![](https://i-blog.csdnimg.cn/blog_migrate/b9747097b256f8d02bb88cf46ed262b9.png)
我的设计思路很简单,解决这个项目只需要三个模块。这三个模块是逐级调用的关系,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 |
很明显,预期和现实有着过大的差距,原因为何?敲代码不熟练,项目流程不熟悉。而且,我做这个项目也没有按照这个流程走。我也不太喜欢这个流程,我喜欢的流程是,自己设计框架,然后写基础代码,在写代码的时候就会注意到很多不敲打时注意不到的细节。基础铺垫好了,再去写设计文档,这样写出来的文档会高一个档次。因为你敲代码的时候会不停的对项目加深理解,理解越深,写出来的需求就越详细,功能设计就越完善。