我们老师最近布置了一项作业,让每个人用Java语言实现整数或是分数的四则运算功能。
具体需求如下:
1、程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
2、每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
3、练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
4、支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。
5、支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。
我认为,整个项目的难点就在于将中缀表达式转化为后缀表达式,并将其计算出来。在这里我运用了逆波兰表达式和调度场算法。
下面是我的部分代码
public int algorithm(String s) {
//放数字
Stack<Integer> stack1 = new Stack<>();
//放操作符
Stack<String> stack2 = new Stack<>();
//存放运算符优先级
HashMap<String, Integer> hashmap = new HashMap<>();
hashmap.put("(", 0);
hashmap.put("+", 1);
hashmap.put("-", 1);
hashmap.put("*", 2);
hashmap.put("÷", 2);
for (int i = 0; i < s.length();) {
//设置可变长的字符串
StringBuffer digit = new StringBuffer();
//将式子字符串切割为c字符
char c = s.charAt(i);
//判断字符是否为10进制数字,将一个数加入digit
while (Character.isDigit(c)) {
digit.append(c);
i++;
c = s.charAt(i);
}
//当前digit里面已经无数字,即当前处理符号
if (digit.length() == 0){
switch (c) {
case '(': {
stack2.push(String.valueOf(c));
break;
}
//遇到右括号了计算,因为(的优先级最高
case ')': {
String stmp = stack2.pop();
while (!stack2.isEmpty() && !stmp.equals("(")) {
int a = stack1.pop();
int b = stack1.pop();
int sresulat = calculate(b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
//符号指向下一个计算符号
stmp = stack2.pop();
}
break;
}
case '=': {
String stmp;
while (!stack2.isEmpty()) {
stmp = stack2.pop();
int a = stack1.pop();
int b = stack1.pop();
int sresulat = calculate(b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
}
break;
}
default: {
String stmp;
while (!stack2.isEmpty()) {
stmp = stack2.pop();
//比较优先级
if (hashmap.get(stmp) >= hashmap.get(String.valueOf(c))) {
int a = stack1.pop();
int b = stack1.pop();
int sresulat =calculate (b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
}
else {
stack2.push(stmp);
break;
}
}
//将符号压入符号栈
stack2.push(String.valueOf(c));
break;
}
}
}
else {
//处理数字
stack1.push(Integer.valueOf(digit.toString()));
continue;
}
i++;
}
//返回栈底得到答案
return stack1.peek();
}
private int calculate(int a, int b, String stmp) { //计算a stmp b的值
int res = 0;
char s = stmp.charAt(0);
switch (s) {
case '+': {
res = a + b;
break;
}
case '-': {
//判断是否产生负数
res = a - b;
break;
}
case '*': {
res = a * b;
break;
}
case '÷': {
if(b==0)
return -1;
//判断是否产生小数
else if(a%b!=0)
return -2;
else
res = a / b;
break;
}
}
return res;
}
这是算法部分
public class Create {
public String createProblem(){ //产生整数式子
Random r = new Random();
String[] opertor = {"+","-","*","÷"};
//操作符的个数
int operatorNum = 3+r.nextInt(3);
//新建数组来保存操作数
int[] number = new int[operatorNum+1];
//操作符的下标
int[] arr = index(operatorNum);
String s = new String();
for(int j=0;j<operatorNum+1;j++){
number[j] = r.nextInt(101);
}
//如果flag=0,则该式子加括号,如果flag=1,则该式子不加括号(自己设定)
int flag = r.nextInt(2);
switch (operatorNum){
case 3:{
if(flag == 0){
s = "(" + number[0] + opertor[arr[0]] + number[1] + ")" +opertor[arr[1]]+ "(" + number[2] + opertor[arr[2]] + number[3] + ")";
}
else{
s = number[0] + opertor[arr[0]] + number[1]+opertor[arr[1]]+number[2] + opertor[arr[2]] + number[3] ;
}
break;
}
case 4:{
if(flag ==0){
s = "(" + "(" + number[0] + opertor[arr[0]] + number[1] + ")" + opertor[arr[1]] + number[2] + ")" + opertor[arr[2]] + "(" + number[3] + opertor[arr[3]] + number[4] + ")";
}
else{
s = number[0] + opertor[arr[0]] + number[1] +opertor[arr[1]] + number[2] + opertor[arr[2]] + number[3] + opertor[arr[3]] + number[4] ;
}
break;
}
case 5:{
if(flag ==1){
s = "(" + "(" + number[0] + opertor[arr[0]]+number[1]+")" + opertor[arr[1]] + number[2] + ")" +opertor[arr[2]]+ "(" + "(" + number[3] + opertor[arr[3]] + number[4] + ")" + opertor[arr[4]] + number[5] + ")";
}
else{
s = number[0] + opertor[arr[0]]+number[1]+ opertor[arr[1]] + number[2] +opertor[arr[2]] + number[3] + opertor[arr[3]] + number[4] + opertor[arr[4]] + number[5] ;
}
break;
}
}
s+="=";
Calculator calculator = new Calculator();
int answer = calculator.algorithm(s);
//判断式子是否符合要求,凡是返回负数的就是不合格的
if(answer>=0){
s+=answer;
}else {
//递归
return createProblem();
}
return s;
}
public int[] index(int n){ //产生操作符的下标数组
Random random = new Random();
int similar=0;
int[] a = new int[n];
for(int j=0;j<n;j++){
a[j] = random.nextInt(4);
}
for(int j=1;j<n;j++){
if(a[0]==a[j]) similar++;
}
//保证一个式子里至少有2个不同的操作符
if(similar==n-1) return index(n);
else {
return a;
}
}
}
产生整数式子
public class ProperFraction {
public String createProblem(){
Random r = new Random();
String[] operator = {"+","-"};
int operatorCount = 3+r.nextInt(3);
int[] index = index2(operatorCount);
int sumx = 1+r.nextInt(10);
int sumy = 1+r.nextInt(20);
int g = maxG(sumx,sumy);
sumx/=g;
sumy/=g;
int[] fenshu = huajian(sumx, sumy);
sumx = fenshu[0];
sumy = fenshu[1];
//第一个数
String s=sumx+"/"+sumy;
for(int i=0;i<operatorCount;i++){
int numx = r.nextInt(10);
int numy = 1+r.nextInt(20);
String currentOpreator = operator[index[i]];
fenshu = huajian(numx, numy);
numx = fenshu[0];
numy = fenshu[1];
if(currentOpreator.equals("+")) {
fenshu = huajian(sumx * numy + sumy * numx, sumy * numy);
sumx = fenshu[0];
sumy = fenshu[1];
}
else { //减法
while(sumx*numy-sumy*numx<0)
{
numx=r.nextInt(10);
numy=1+r.nextInt(20);
g=maxG(numx,numy);
numx/=g;
numy/=g;
}
sumx=sumx*numy-sumy*numx;
sumy=sumy*numy;
}
s+=currentOpreator+numx+"/"+numy;
}
g = maxG(sumx,sumy);
sumx/=g;
sumy/=g;
if(sumx==0) s+="="+sumx;
else if(sumx==1&&sumy==1) s+="="+sumx;
else s+="="+sumx+"/"+sumy;
return s;
}
public int maxG(int fenzi,int fenmu)//求最大公因数
{
while (fenmu != 0) {
int r = fenzi % fenmu;
fenzi = fenmu;
fenmu = r;
}
return fenzi;
}
public int[] index2(int n) {
Random random = new Random();
int similar = 0;
int[] a = new int[n];
for (int j = 0; j < n; j++) {
a[j] = random.nextInt(2);
}
for (int j = 1; j < n; j++) {
if (a[0] == a[j]) similar++;
}
if (similar == n - 1) return index2(n);
else {
return a;
}
}
public int[] huajian(int fenzi, int fenmu){
Random r = new Random();
while (fenzi >= fenmu) {
fenzi = 1 + r.nextInt(10);
fenmu = 1 + r.nextInt(20);
int g = maxG(fenzi, fenmu);
fenzi = fenzi / g;
fenmu = fenmu / g;
}
int[] fenshu = new int[2];
fenshu[0] = fenzi;
fenshu[1] = fenmu;
return fenshu;
}
}
产生分数式子
这三块便是整个项目最核心的部分便是这三个部分。
接下来就是项目功能的展示
在命令行进行测试
得到result.txt文件
下面是我的PSP
PSP2.1 | 任务内容 | 计划共完成需要的时间(h) | 实际完成需要的时间(h) |
Planning | 计划 | 2 | 3 |
· Estimate | · 估计这个任务需要多少时间,并规划大致工作步骤 | 2 | 3 |
Development | 开发 | 46.5 | 69.5 |
· Analysis | · 需求分析 (包括学习新技术) | 2 | 4 |
· Design | · 具体设计 | 1 | 1 |
· Coding | · 具体编码 | 38 | 46 |
· Code Review | · 代码复审 | 0.5 | 0.5 |
· Test | · 测试(自我测试,修改代码,提交修改) | 5 | 18 |
Reporting | 报告 | 0.8 | 1 |
· Size Measurement | · 计算工作量 | 0.3 | 0.5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0.5 | 0.5 |
改进:
我在生成括号的地方偷了个小懒,没有用生成随机数的方式来让式子随机生成括号的位置,我想,在以后,这是可以再进行改进的地方
个人感悟:
这次的项目让我感触颇多,同时也让我收获了许多。
由于很久没有写过Java程序了,以至于我都忘记了idea里的环境怎么配,只得一点一点从头再来。在开始写这个作业的时候,我有点无从下手,于是就一个一个地把需求在纸上罗列了出来,就是这样
这才使我的思路逐渐清晰。在代码基本完成,开始测试的时候又出了很多的问题,有时我在命令行测试的时候会出现一些莫名其妙的错误,比如找不到主类,数组下标越界,甚至直接告诉我我的内存不够了,在解决这些问题的时候真的是心力交瘁,有的时候甚至想,算了算了,直接随便找一份交上去算了,但是后来还是休息之后,继续和这些bug作斗争!
虽说这次作业耗费了很多的精力,但是在这次的项目中,我收获了宝贵的经验以及技术的大幅进步,哈哈哈哈哈,程序员不就是这样嘛,在一次又一次的实战中不断进步,不断成长!