这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | 组队实现一个自动生成小学四则运算题目的程序 |
成员 | 3122004487 林丙昆 |
成员 | 3122004502 赵衍锴 |
GitHub地址:地址
PSP2.1表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 980 | 1030 |
· Analysis | · 需求分析 (包括学习新技术) | 50 | 20 |
· Design Spec | · 生成设计文档 | 30 | 20 |
· Design Review | · 设计复审 | 20 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 60 | 50 |
· Coding | · 具体编码 | 600 | 750 |
· Code Review | · 代码复审 | 50 | 50 |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 | 120 |
Reporting | 报告 | 140 | 140 |
· Test Report | · 测试报告 | 100 | 100 |
· Size Measurement | · 计算工作量 | 20 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
· Total | · 合计 | 1140 | 1180 |
代码说明
- Main类:主函数类
- InputCheck类:检查输入是否正确
- OperatorCreate类:随机生成算术运算符
- NumberCreate类:随机生成运算数
- Expression类:将运算符数组、运算数数组和括号拼成一个运算式字符串
- Calculation类:计算算式的结果,包括真分数化简,带分数化假分数,分数计算等
- File类:将算式及答案写入文件/计算正确率并写入文件
主要流程
关键代码
getExpressResult:该代码使用了两个栈:一个用于存储运算符(operators),另一个用于存储操作数(Fraction),遍历字符数组,当遇到左括号时,将其压入operators栈。当遇到右括号时,弹出operators栈中的运算符并进行运算,直到遇到左括号为止。运算过程中,从fractions栈中取出两个分数作为操作数,计算后的结果再压回fractions栈。当遇到运算符时,根据运算符的优先级与operators栈顶的运算符比较,如果当前运算符的优先级更高或相等,则将其压入operators栈;如果更低,则先弹出栈顶的运算符进行计算,直到遇到优先级更低的运算符或栈为空。当遇到数字字符时,通过构建StringBuilder来累积完整的操作数。如果操作数中包含/,则将其分割为分子和分母,并创建一个Fraction对象压入fractions栈;如果不包含/,则默认分母为1。遍历完所有字符后,如果operators栈中还有运算符,则继续从fractions栈中取出操作数进行计算,直到operators栈为空。
点击查看代码
public static String getExpressResult( String express){
//运算符栈,用于存放运算符包括
Stack<Character> operators = new Stack<>();
//操作数栈,用于存放操作数
Stack<Fraction> fractions = new Stack<>();
//将表达式字符串转成字符数组
char[] chars = express.toCharArray();
//遍历获取处理
for (int i=0;i<chars.length;i++) {
//获取当前的字符
char c = chars[i];
if(c=='('){
//如果是左括号,入栈
operators.push(c);
}else if(c==')'){
//当前字符为右括号
//当运算符栈顶的元素不为‘(’,则继续
while(operators.peek()!='('){
//拿取操作栈中的两个分数
Fraction fraction1 = fractions.pop();
Fraction fraction2 = fractions.pop();
//获取计算后的值
Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if(result.getNumerator()<0){
//保证运算过程不出现负数
return "#";
}
//将结果压入栈中
fractions.push(result);
}
//将左括号出栈
operators.pop();
}else if(c=='+'||c=='-'||c=='*'||c=='÷'){
//是运算符
//当运算符栈不为空,且当前运算符优先级小于栈顶运算符优先级
while(!operators.empty()&&!priority(c, operators.peek())){
//拿取操作栈中的两个分数
Fraction fraction1 = fractions.pop();
Fraction fraction2 = fractions.pop();
//获取计算后的值
Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if(result.getNumerator()<0){
return "#";
}
//将结果压入栈中
fractions.push(result);
}
//将运算符入栈
operators.push(c);
}else{//是操作数
if(c>='0'&&c<='9'){
StringBuilder buf = new StringBuilder();
//这一步主要是取出一个完整的数值 比如 2/5、9、9/12
while(i< chars.length&&(chars[i]=='/'||((chars[i]>='0')&&chars[i]<='9'))){
buf.append(chars[i]);
i++;
}
i--;
//到此 buf里面是一个操作数
String val = buf.toString();
//标记‘/’的位置
int flag = val.length();
for(int k=0;k<val.length();k++){
if(val.charAt(k)=='/'){
//当获取的数值存在/则标记/的位置,便于接下来划分分子和分母生成分数对象
flag = k;
}
}
StringBuilder numeratorBuf = new StringBuilder();
StringBuilder denominatorBuf = new StringBuilder();
for(int j=0;j<flag;j++){
numeratorBuf.append(val.charAt(j));
}
//判断是否为分数
if(flag!=val.length()){
for(int q=flag+1;q<val.length();q++){
denominatorBuf.append(val.charAt(q));
}
}else{
//如果不是分数则分母计为1
denominatorBuf.append('1');
}
fractions.push(new Fraction(Integer.parseInt(numeratorBuf.toString()), Integer.parseInt(denominatorBuf.toString())));
}
}
}
while(!operators.empty()){
Fraction fraction1 = fractions.pop();
Fraction fraction2 = fractions.pop();
//获取计算后的值
Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if(result.getNumerator()<0){
return "#";
}
//将结果压入栈中
fractions.push(result);
}
//计算结果
Fraction result = fractions.pop();
//获取最终的结果(将分数进行约分)
return getFinalResult(result);
}
calculate:分数运算,加法:第一个分数的分子乘以第二个分数的分母加上第二个分数的分子乘以第一个分数的分母;分母为两个分母相乘。减法:第一个分数的分子乘以第二个分数的分母减第二个分数的分子乘以第一个分数的分母;分母为两个分母相乘。乘法:分子分母各相乘。除法:第一个分数的倒数与第二个分数相乘。
点击查看代码
private static Fraction calculate(Character opt, int num1, int den1, int num2, int den2){
//结果数组,存放结果的分子与分母
int[] result = new int[2];
switch (opt){
case'+':
result[0] = num1*den2 + num2*den1; result[1]= den1*den2;
break;
case '-':
result[0] = num2*den1 - num1*den2; result[1]= den1*den2;
break;
case '*':
result[0] = num1*num2; result[1] = den1*den2;
break;
case '÷':
result[0] = num2*den1; result[1] = num1*den2;
break;
}
return new Fraction(result[0],result[1]);
}
性能分析
运行测试
生成题目
生成正确答案
写上部分错误的答案然后测试答案
生成测试结果
项目小结
- 我们实现了基础的四则运算题目生成与答案验证功能。然而,在追求更高性能与效率的过程中,由于团队仅由两人组成,编程资源相对有限,特别是在算法的优化与提升方面,我们未能达到预期的深度与广度。这一局限在面对特定需求时尤为明显,导致项目在某些情况下难以迅速达成目标。
- 相较于个人独立工作,结对合作显著提升了工作效率。我们能够即时分享想法,快速解决遇到的问题,有效缩短了问题解决周期。在合作过程中,我们频繁地讨论算法实现,这不仅加深了我们对问题的理解,还让我们有机会了解对方的思考方式和编程习惯,从而激发了新的创意和灵感。