结对项目-生成小学四则运算题目程序
一、GitHub项目地址:
https://github.com/fightcypress/caculate
二、PSP:
|
PSP |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
|
Planning |
计划 |
30 |
40 |
|
· Estimate |
· 估计这个任务需要多少时间 |
30 |
40 |
|
Development |
开发 |
1690 |
1605 |
|
· Analysis |
· 需求分析 (包括学习新技术) |
120
|
150 |
|
· Design Spec |
· 生成设计文档 |
30 |
30 |
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
30 |
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
10 |
|
· Design |
· 具体设计 |
60 |
55 |
|
· Coding |
· 具体编码 |
1200 |
1440 |
|
· Code Review |
· 代码复审 |
120 |
100 |
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
90 |
|
Reporting |
报告 |
135 |
145 |
|
· Test Report |
· 测试报告 |
30 |
45 |
|
· Size Measurement |
· 计算工作量 |
15 |
10 |
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
90 |
90 |
|
合计 |
|
1855 |
1790 |
三、效能分析:
生成一万道程序时,情况如下:


100万道:


四、设计实现过程:
解题思路:
1.表达式的生成:每个表达式的运算符个数不超过3个,生成两个个运算符的表达式可以调用生成单个运算符表达式的函数,生成三个运算符的表达式可以调用生成两个运算符表达式的函数。如:(1+2)*3 可以由 (1+2) 和 3 结合 * 生成。
2.计算表达式的值:用逆波兰表达式计算,如:(1+2)*3 , 用逆波兰表达式表达:12+3* ,先计算子式 "1+2",再计算 "1+2"的结果和"3"的乘积。在计算每个子式时,如果是减法,要保证每个子式的结果非负,如果是除法,要保证为真分数。
五、关键代码及设计过程:
项目结构:

randExpression类调用numAndoperator类生成表达式,
caculExp调用caculRule来计算逆波兰表达式
caculRule类包含各种计算表达式的函数。
生成表达式函数:
public Expression getExpression(int range){
int operatorNum = (int)(Math.random()*3); //操作符数目
if(operatorNum == 0){
return get1OperatorExpression(range,0,1);
}else if(operatorNum == 1){
//sign:0:两个操作符不带括号 1:两个操作符带左括号 2:两个操作符带右括号
int sign = (int)(Math.random()*3);
if(sign == 0) return get2OperatorExpression(range,0,1);
else if(sign == 1) return get2OperatorExpressionWithLeftBrackets(range,0,1);
else return get2OperatorExpressionWithRightBrackets(range,0,1);
}else{
//sign:0:三个操作符不带括号 1:三个操作符带左括号 2:三个操作符带右括号
int sign = (int)(Math.random()*3);
if(sign == 0) return get3OperatorExpression(range,0,1);
else if(sign == 1) return get3OperatorExpressionWithLeftBrackets(range,0,1);
else return get3OperatorExpressionWithRightBrackets(range,0,1);
}
}
生成一个操作数函数:
/*
生成单操作符表达式
numBeginPosion:操作数在表达式字符串数组中的位置
operatorPosition:运算符在表达式字符串数组中的位置
*/
public Expression get1OperatorExpression(int range,int numBeginPosion,int operatorPosition){
Expression expression = new Expression();
String operator = nao.getOperatorChar();
String leftOperatorNumber = nao.getOperatorNum(range);
String rightOperatorNumber = randrule.getRightOperatorNum(operator,nao.getOperatorNum(range), range);
expression.getOperatorNum()[numBeginPosion] = leftOperatorNumber;
expression.getOperatorNum()[numBeginPosion+1] = rightOperatorNumber;
expression.getOperator()[operatorPosition] = operator;
return expression;
}
获取随机数:
//获得随机数
public int getRandomNum(int range){
return (int)(Math.random()*range);
}
//获得运算符号
public String getOperatorChar(){
int temp = (int)(Math.random()*4);
return opetator[temp];
}
//生成真分数
public String getTruefraction(int range){
String trueFraction = "";
//生成分母
Integer mother = getRandomNum(range);
while(true){
if(mother <= 1){
mother = getRandomNum(range);
}else{
break;
}
}
//生成分子
Integer son = getRandomNum(mother);
while(true){
if(son == 0){
son = getRandomNum(mother);
}else{
break;
}
}
//拼接
trueFraction += son.toString();
trueFraction += "/";
trueFraction += mother.toString();
return trueFraction;
}
//生成假分数
public String getFalsefraction(int range){
String falseFraction = ""; //假分数
int temp = getRandomNum(range);
String part; //假分数整数部分
while(true){
if(temp == 0){
temp = getRandomNum(range);
}else{
break;
}
}
//生成真分数部分
part = getTruefraction(range);
falseFraction += ((Integer)temp).toString();
falseFraction += "'";
falseFraction += part;
return falseFraction;
}
获取逆波兰表达式:
public String[] getRPN(Expression expression){
Stack<String> stack = new Stack<>();
String[] RPN = new String[9]; //逆波兰表达式
//将expression转变为字符串数组
String[] tempExpression = expression.getStringExpression();
//获得字符串数组的长度
int length = caculRule.getLength(tempExpression);
int index = 0;
for (int i = 0; i < length; i++) {
String temp;
String string = tempExpression[i];
switch (string) {
case "(":
stack.push(string);
break;
case "+":
case "-":
while (stack.size() != 0) {
temp = stack.pop();
if (temp.equals("(")) {
stack.push("(");
break;
}
RPN[index++] = temp;
}
stack.push(string);
break;
case "*":
case "÷":
while (stack.size() != 0) {
temp = stack.pop();
if (temp.equals("(") || temp.equals("+") || temp.equals("-")) {
stack.push(temp);
break;
} else {
RPN[index++] = temp;
}
}
stack.push(string);
break;
case ")":
while (stack.size() != 0) {
temp = stack.pop();
if (temp.equals("("))
break;
else
RPN[index++] = temp;
}
break;
default:
if(!string.contains("'") && !string.contains("/")){
string += "/1";
}
RPN[index++] = string;
}
}
while (stack.size() != 0) {
String temp = stack.pop();
RPN[index++] = temp;
}
return RPN;
}
计算表达式:
public String caculateRPN(String[] rpn){
Stack<String> stack = new Stack<>();
//获得逆波兰表达式字符串数组的长度
int length = caculrule.getLength(rpn);
String temp = null;
for(int i = 0; i < length; i++){
if(rpn[i].length() > 1){ //长度大于1,操作数入栈
stack.push(rpn[i]);
}else{
String leftOp = caculrule.becomeFalsefraction(stack.pop()); //变为假分数计算
String rightOp = caculrule.becomeFalsefraction(stack.pop());
String result = caculateExp(rpn[i],leftOp,rightOp);
if(result == null) return null;
stack.push(result);
}
}
String answer = stack.pop();
Integer newSon = Integer.parseInt(caculrule.getFractionSon(answer));
Integer Lcm = Integer.parseInt(caculrule.getFractionMom(answer));
return caculrule.getAnswer(newSon,Lcm);
}
//计算逆波兰表达式的每个子式
public String caculateExp(String operator,String leftOperatorNum,String rightOperatorNum){
switch (operator){
case "+": return caculrule.add(leftOperatorNum,rightOperatorNum);
case "-": return caculrule.sub(leftOperatorNum,rightOperatorNum);
case "*": return caculrule.multiply(leftOperatorNum, rightOperatorNum);
case "÷": return caculrule.divide(leftOperatorNum, rightOperatorNum);
default: return null;
}
}
计算逆波兰表达式子式:
//子表达式计算
public String calulate(int tag,Integer leftMom,Integer leftSon,Integer rightMom,Integer rightSon){
Integer Lcm = getLcm(leftMom,rightMom); //获取左操作数分母和右操作数分母最小公倍数
Integer newSon = 0; //结果分子
if(tag == 1){
//加法
newSon = leftSon*(Lcm/leftMom) + rightSon*(Lcm/rightMom);
return newSon.toString()+"/"+Lcm.toString();
}else if(tag == 2){
//减法
newSon = leftSon*(Lcm/leftMom) - rightSon*(Lcm/rightMom);
if(newSon < 0) return null; //计算过程产生负数
return newSon.toString()+"/"+Lcm.toString();
}else if(tag == 3){
//乘法
Integer multiSon = leftSon*rightSon;
Integer multiMom = leftMom*rightMom;
return multiSon.toString()+"/"+multiMom.toString();
}else if(tag == 4){
//除法
if(rightSon == 0) return null;
Integer diviSon = leftSon*rightMom;
Integer diviMom = leftMom*rightSon;
if(diviSon > diviMom) return null; //假分数
return diviSon.toString()+"/"+diviMom.toString();
}else{
return null;
}
}
io操作:
private static void fileIo(String filename, String[] write) {
File dir = new File("./"+filePath);
if(!dir.exists()){
dir.mkdirs();
}
File f = new File(dir,filename);
if(!f.exists()){
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
StringBuffer sb=new StringBuffer();
System.out.println("writeContent--->长度"+write.length);
for(int i = 0;i<write.length;i++){
sb.append((i+1)+":"+write[i]+"\r\n");
}
FileOutputStream out=new FileOutputStream(f,false);
out.write(sb.toString().getBytes("utf-8"));
out.flush();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
对比:
public static String getUsersAnswer(String[] usersAnswer,String[] rightAnswer){
String rightAnswerResport = "Correct: ",rightAnswerList = "正确的题号"; //答对题目
String wrongAnswerResport = "Wrong",wrongAnswerList = "错误的题号"; //答错题目
int rightNum = 0,wrongNum = 0; //记录正确和错误的个数
for(int i = 0;i<usersAnswer.length;i++){ //统计答案
if(usersAnswer[i].equals(rightAnswer[i])){//答对
rightNum++;
rightAnswerList = rightAnswerList+ (i+1)+",";
}else{//答错
wrongNum++;
wrongAnswerList = wrongAnswerList + (i+1) +",";
}
}
rightAnswerResport = rightAnswerResport+rightNum+"-------"+rightNum+"道题正确, "+rightAnswerList+"\n";
wrongAnswerResport = wrongAnswerResport+wrongNum+"-------"+wrongNum+"道题正确,"+wrongAnswerList+"\n";
return rightAnswerResport+wrongAnswerResport;
}
六、测试运行:
生成一万道题目:



10道题目:

校对答案:

输出到文件:


七、项目总结:
本次项目是第一次结对编程,由于疫情影响,交流肯定受到了很大的影响,好在如今通讯工具发达,还是能够及时沟通讨论问题。项目一开始,我们没有什么思绪,通过查阅资料、分享资料,让自己有一个比较清晰的思路。这是第二次项目,对比第一次个人项目,我们二人都感觉对项目整体有了更好的把控,做事更有计划。
遇到的困难:
1.对于表达式的查重,我们没有一个比较好的解决思路,所以最终也没有实现该功能。
2.在生成表达式时,一开始考虑直接在生成表达式的同时判断表达式非负以及表达式为真分数,但没有好的解决方法,要靠遍历多种情况,后来在计算逆波兰表达式的时候直接对子式进行判断,解决了该问题。
3.我们两人都没有接触过图形化界面,因此也没有实现。
润柏闪光点:润柏对整个项目结构有着清晰的思路,同时有着扎实的编程技术,遇到问题时,能耐住性子解决,同时可以和同伴及时沟通,鼓励同伴,能带领队员前进。
堉涵闪光灯:有着较好的信息搜索能力,在遇到问题时,能静下心听对方讲解,较有耐心。


浙公网安备 33010602011771号