githup传送门:https://github.com/Bubblegod/FormulationCalculation
项目成员:梁竞 袁智杰
1、项目要求
题目:实现一个自动生成小学四则运算题目的命令行程序。
需求:
1. 使用-n参数控制生成题目的个数。
2. 使用-r参数控制题目中数值(自然数、真分数和真分数分母)的范围。
3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
5. 每道题目中出现的运算符个数不超过3个。
6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。
8. 程序应能支持一万道题目的生成。
9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。
2、PSP2.1表格
PSP2.1
Personal Software Process Stages
预估耗时(分钟)
实际耗时(分钟)
Planning
计划
20
40
· Estimate
· 估计这个任务需要多少时间
10
10
Development
开发
20
30
· Analysis
· 需求分析(包括学习新技术)
30
30
· Design Spec
· 生成设计文档
20
40
· Design Review
· 设计复审(和同事审核设计文档)
10
10
· Coding Standard
· 代码规范(为目前的开发制定合适的规范)
20
20
· Design
· 具体设计
20
40
· Coding
· 具体编码
400
600
· Code Review
· 代码复审
10
30
· Test
· 测试(自我测试,修改代码,提交修改)
30
30
Reporting
报告
30
50
· Test Report
· 测试报告
20
30
· Size Measurement
· 计算工作量
20
20
· Postmortem & Process Improvement Plan
· 事后总结,并提出过程改进计划
20
10
合计
680
990
3、设计实现过程
1.Mian类用于启动整个程序的所有操作。
2.BinaryTree类实现二叉树数据结构,以及所有二叉树操作的方法
3.ClaculateExpression类实现计算表达式的最终结果,并实现方法返回每条式子最终结果的答案
4.DataOperation对计算结果进行规范化(分数的格式化,若为假分数,统一化为带分数或整数格式,否则化为最简真分数)
5.ExprssionGenerator用于构造出最初的表达式
6.ExpressionOperation该类用来处理初步拼合而成的四则运算表达式,使其满足作业要求,并将初步拼合而成的四则运算表达式按照优化策略统一规范化,便于之后的查重处理
7.FileUtil用于处理文件的输入输出
4、代码说明
第一部分:等式的生成分为四个部分:
1.确定运算数的个数,符号数的个数
2.确定运算数的个数之后,确定整数与分数的个数
3.随机生成整数、分数、符号
4.把整数、分数、符号随机地,有序地排列组合,放入一个String的变量里面
voidintegerListGenerating() {inti;for (i = 0; i < randomNumber_integer; i++) {inttemp;
Random randomTemp= newRandom();
temp= randomTemp.nextInt(this.maximunValue);
integerList.add(Integer.toString(temp));
}
}voidfractionListGenerating() {inti;
i= 0;while (i
Random randomTemp1= newRandom();
Random randomTemp2= newRandom();
temp1= randomTemp1.nextInt(this.maximunValue - 1);
temp1+= 1;
temp2= randomTemp2.nextInt(this.maximunValue - 2);
temp2+= 2;if (!((temp1 % temp2 == 0) || (temp2 % temp1 == 0))) {
temp1= temp1 /getGCD(temp1, temp2);
temp2= temp2 /getGCD(temp1, temp2);if ((temp1 != temp2) && (temp1 != 0) && (temp2 != 0)) {if (temp1 >temp2) {
fractionList.add(Integer.toString(temp1/ temp2) + "`"
+ Integer.toString(temp1 % temp2) + "/"
+Integer.toString(temp2));
}else{
fractionList.add(Integer.toString(temp1)+ "/"
+Integer.toString(temp2));
}
i++;
}
}
}for (i = 0; i <= randomNumber_fraction; i++) {inttemp;
Random randomTemp= newRandom();
temp= randomTemp.nextInt(randomNumber_integer + 1);
position[i]=temp;
}
}voidsignListGenerating() {inti;for (i = 0; i < randomNumber_Number - 1; i++) {inttemp;
Random randomTemp= newRandom();
temp= randomTemp.nextInt(4);if (temp == 0) {
signList.add("+");
}else if (temp == 1) {
signList.add("-");
}else if (temp == 2) {
signList.add("*");
}else{
signList.add("÷");
}
}
}
第二部分:将中缀表达式转化成后缀表达式
public staticString infixToSuffix(String infixExpression) {//创建操作符堆栈
Stack characterStack = new Stack();//要输出的后缀表达式字符串
String suffix = "";//传入的中缀表达式的长度
int length =infixExpression.length();for (int i = 0; i < length; i++) {//临时字符变量
chartemp;//获取该中缀表达式中的每一个字符并进行相对应的判断
char ch =infixExpression.charAt(i);switch(ch) {//忽略空格
case ' ':break;//如果是左括号直接压入栈中
case '(':
characterStack.push(ch);break;//碰到'+' '-',将栈中的所有运算符全部弹出去,直至碰到左括号为止,输出到队列中去
case '+':case '-':while (characterStack.size() != 0) {
temp=characterStack.pop();if (temp == '(') {//如果推出来的那个字符刚好是左括号,那么就重新将左括号放回栈中,并终止循环
characterStack.push('(');break;
}
suffix+=temp;
}//没有进入循环说明当前为第一次进入或者前面运算都有括号等情况所导致//将当前符号压入栈中
characterStack.push(ch);
suffix+= "#";break;//如果是乘号或者除号,则弹出栈中所有运算符,直到碰到加号、减号、左括号为止,最后将该运算符压入栈中
case '*':case '÷':while (characterStack.size() != 0) {
temp=characterStack.pop();//只有比当前优先级高的或者相等的才会弹出到输出队列,遇到加减左括号,直接停止当前循环
if (temp == '+' || temp == '-' || temp == '(') {
characterStack.push(temp);break;
}else{
suffix+=temp;
}
}//没有进入循环说明当前为第一次进入或者前面运算都有括号等情况所导致//将当前符号压入栈中
characterStack.push(ch);
suffix+= "#";break;//如果碰到的是右括号,则距离栈顶的第一个左括号上面的所有运算符将被弹出栈并抛弃左括号(既不需要将左括号输出到队列中也不需要将右括号压入栈中)
case ')':while (!characterStack.isEmpty()) {
temp=characterStack.pop();if (temp == '(') {break;
}else{
suffix+=temp;
}
}break;//默认情况,如果读取到的是操作数,则直接送至输出队列
default:
suffix+=ch;break;
}
}//最后如果栈中依然还有运算符的话,则把剩余运算符一次弹出,送至输出序列
while (characterStack.size() != 0) {
suffix+=characterStack.pop();
}returnsuffix;
}
第三部分:将后缀表达式转化成二叉树,利用二叉树的优先级,将同等级的运算数进行排序,从而把中缀表达式不相同的的式子,转化成为后缀表达式一样的式子,便可找出重复的式子,并剔除掉。
/*** function:优化策略一(按规则优化二叉树,使其计算过程中不会出现负数)
*
*@paramtree
*@return
*/
publicBinaryTree adjustmentTree(BinaryTree tree) {if (tree != null) {
String leftValue=getTreeValue(tree.getLeftTree());
String rightValue=getTreeValue(tree.getRightTree());//若右子树的值大于左子树,则交换子树
if (rightValue.equals("") || leftValue.equals("")) {//表示此时已经递归到叶子了,直接返回即可
returntree;
}else if (dataOperation.calculateData(rightValue, leftValue, "?").equals("1")) {
tree=swapChildTree(tree);
}
tree.setLeftTree(adjustmentTree(tree.getLeftTree()));
tree.setRightTree(adjustmentTree(tree.getRightTree()));
}returntree;
}/*** function:优化策略二(中序遍历二叉树,转化为符合预定运算顺序的带括号的表达式)
*
*@paramtree
*@return
*/
publicString treeToExpression(BinaryTree tree) {
String left= "";
String right= "";
String expression= "";if (tree != null) {if (tree.getLeftTree() != null && tree.getRightTree() != null) {
String leftRoot=tree.getLeftTree().getRootData();
String rightRoot=tree.getRightTree().getRootData();if (leftRoot.equals("+") || leftRoot.equals("-") || leftRoot.equals("*") || leftRoot.equals("÷")) {
left= "(" + treeToExpression(tree.getLeftTree()) + ")";
}else{
left=treeToExpression(tree.getLeftTree());
}if (rightRoot.equals("+") || rightRoot.equals("-") || rightRoot.equals("*") || rightRoot.equals("÷")) {
right= "(" + treeToExpression(tree.getRightTree()) + ")";
}else{
right=treeToExpression(tree.getRightTree());
}
}
expression= left + tree.getRootData() +right;
}returnexpression;
}
5、测试运行
这是题目生成个数100个,数值范围是10的情况
这是生成题目10000条的情况,经过大量的测试,发现。。。。。