软件工程导论第一次个人作业!
0.0项目基本任务
使用JAVA编程语言,独立完成一个3到5个运算符的四则运算练习的软件。
软件基本功能要求如下:
• 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号±÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
• 每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
• 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
• 当程序接收的参数为4时,以下为一个输出文件示例。
2018010203
13+17-1=29
1115-5=160
3+10+4-16=1
15÷5+3-2=4
软件附加功能要求如下:(请有余力的同学完成)
• 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。
• 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。
1.0我的整体思路和基本模块:
在百度了一些博客之后,我基本确定了思路:
1.用两个数组结合来生成运算式中的数字和运算符,并按照一定格式插入左右括号;
2.运用逆波兰算法将中缀表达式转换为后缀表达式,并使用栈对后缀表达式进行计算,并在运算过程中判断排除负数和小数中间结果的出现;
3.最后在Main函数中将结果输出到指定文件;
2.0我的学习和开发过程:
阶段一:
首先我参考了这篇博客,开始大概懂了几个方法后开始编码,还对这篇的方法和整体思路特别有自信,觉得弄懂他的方法就完成了,后来发现是我太天真了,在测试的时候有很多问题。在一边编码的时候由于没有从总体上理解清楚后再下手,使得开发效率低,花多时间调试,浪费很多时间,表达式生成后运算符和括号位置混乱并且没有结果,最后决定另寻他路。
(https://www.cnblogs.com/Fuenli/p/8605497.html)
阶段二:
由于自身java基础很差,我花了很多时间研究别人的博客,而且第一阶段的开发又浪费很时间,所以这时距离交作业很近了,我就想赶紧再找一个思路相近的再学习学习,于是根据上课时老师的推荐找到了这个学姐的代码:(https://coding.net/u/wanghz499/p/2016012032week2-2/git/tree/master/src)
其中我没有实现"ProperFraction()",其余思路类似:
很巧妙地产生运算式:
//相当于固定了运算式的运算符位置,即格式固定
switch (operatorCount){
case 3:{
if(choose==0){
s=num[0]+operator[index[0]]+"("+"("+num[1]+operator[index[1]]+num[2]+")"+operator[index[2]]+num[3]+")";//1+((2×3)-4)型
}else s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×(3+4)型
break;
}
case 4:{
if(choose==0){
s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+num[4]+operator[index[3]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×3÷(4-1)型
}else s=num[4]+operator[index[3]]+"("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//3×(1+2)+(4÷2)型
break;
}
case 5:{
if(choose==0){
s="("+num[0]+operator[index[0]]+num[1]+operator[index[4]]+num[5]+")"+operator[index[1]]+"("+num[4]+operator[index[3]]+num[2]+")"+operator[index[2]]+num[3];//(6+2×3)-(1+2)×3型
}else s="("+num[0]+operator[index[0]]+"("+num[1]+operator[index[1]]+num[2]+operator[index[2]]+num[3]+")"+")"+operator[index[3]]+"("+num[4]+operator[index[4]]+num[5]+")";//(1+(2×3+4))-(6÷3)型
break;
}
}
并且 保证式子里至少有2个不同操作符的方法:
private static int[] index(int n,int m){ //产生操作符的下标数组
Random random = new Random();
int similar=0;
int[] a = new int[n];
for(int j=0;j<n;j++){
a[j] = random.nextInt(m);
}
for(int j=1;j<n;j++){
if(a[0]==a[j]) similar++;
}
if(similar==n-1) return index(n); //保证一个式子里至少有2个不同的操作符,若所有操作符下标都一样,则重新产生操作符下标
else {
return a;
}
}
虽然都是核心使用逆波兰表达式,但这个是在中缀转后缀的同时进行计算,
对负数和小数的判断:
(1)在计算时由最基本的运算单元返回的结果来实现,若不符合标准就(return -1;),
case('-'):{
res = a - b; //产生负数就不合格
break;
}
case('÷'):{
if(b==0)
return -1;
else if(a%b!=0) //产生小数就不合格
return -2;
else
res = a / b;
break;
}
(2)这样在运算的方法中只要得到了小于0的返回值也返回状态值为-1,
int a = stack1.pop();
int b = stack1.pop();
int sresulat =calculate (b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
(3)在生成运算式的方法中用一个if语句判断此时的结果,若为负数则递归调用生成函数一直到合格为止。
Calculator c = new Calculator();
int sum = c.Cal(s);
s += sum;
if(sum>=0){ //判断式子是否符合要求,凡是返回负数的就是不合格的
s+=sum;
}else {
return format(); //递归直到产生合格的式子
}
return s;
后来我又觉得既然已经弄懂了她的想法,想自己改进一下,就又搜了搜中缀转后缀这里,然后找到了这篇让我感觉这个理解起来很透彻:
(https://www.cnblogs.com/fuck1/p/5995857.html)
理解清楚后自己练习一下,在改了很久以后发现这个方法只能处理像(2*3+5=?)这种只含有一位数字的运算,这里我也花了不少时间
Pattern pattern = Pattern.compile("\\d+||(\\d+\\.\\d+)");
// 将后缀表达式分割成字符串数组,此处直接使用空白也可以对字符串进行分割!!
String[] strings = exp.split("");
主要问题是我发现这里的 split(" ") 会将 23 这样的数分割成 2 和 3,接下来就无法计算了;学习了用pattern 正则匹配的方法,我尝试着百度split()函数怎样将运算式中的运算符和数字分开,应该是知识储备不够,这个问题没有解决。。。
阶段三:
这时我想再好好研究一下第一次放弃的那个方法,再试试吧;
最终的结果是我没有找到合适的方法排除小数和负数,当式子中没有出现"-" ," ÷"时计算是正确的,希望以后可以改进出来吧,多多学习和练习。
学习到的小知识,也还算有收获:
1.正则表达式:http://www.runoob.com/java/java-regular-expressions.html
2.输出流:https://www.cnblogs.com/fnz0/p/5423201.html
3.类型转换:https://blog.youkuaiyun.com/u010502101/article/details/79162587
4.包括命令行cmd的使用也基本get了
个人总结
首先由于知识和编程经验不足,整个作业效率很低,而且大多数都是看别人的思路后一点点学习的,java学习基本空白,练习的又少;另一方面,我在真正去做的时候也有偷懒的心理,有的时候知其然而不知其所以然,项目整体构思不够清楚,到写的时候要花很多时间,然而编写完其实只是完成了20%的工作量,甚至是无用功,测试和修改的时间真的是比想象的多很多,要解决太多的问题了,因此以后开发时我会先分析清楚再下手;对于以后的学习我觉得还是要采用项目驱动式,一边做一边学,更有针对性,实践出真知!!!
*主要花费的时间如下:
**
任务内容 …… 计划与实际
计划 : 10 --------》 20
开发 : 685--------》 2238(~35h)
具体设计 : 40 --------》 100
具体编码 : 660 --------》 (7+7+5)*60=1140
测试与修改: 200--------》 420+300=720
谢谢观看~