一、题目描述:
       实践能力的提高当然就是得多动手了,那么就从第一个个人项目开始吧,用一周的时间完成一个基于控制台的四则运算程序,实现一个自动生成小学四则运算题目的命令行程序。
从《构建之法》第一章的 “程序” 例子出发,像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”,满足以下需求:
(以下参考博客链接:http://www.cnblogs.com/jiel/p/4810756.html
 
1. 使用 -n 参数控制生成题目的个数,例如
       Myapp.exe -n 10 -o Exercise.txt
将生成10个题目。
2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 
      Myapp.exe -r 10
 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数
4. 每道题目中出现的运算符个数不超过3个。
5. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
     1. 四则运算题目1
     2. 四则运算题目2
          ……
 
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
6. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
    1. 答案1
    2. 答案2
 
    特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
7. 程序应能支持一万道题目的生成。
8. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,并会输出所有题目中重复的题目,输入参数如下:
     Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt -o Grade.txt
 
统计结果输出到文件Grade.txt,格式如下:
 
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
Repeat:2
RepeatDetail:
(1)   2,45+32  Repeat 3,32+45                    
(2)   5,3+(2+1)  Repeat 7,1+2+3
 
解释:
Correct: 5 ----5道题目正确,正确的题号 1,3,5,7,9
Wrong:5 -----5道题目错误,错误的题号 2,4,6,8,10
Repeat:2   2---组题目重复
(1) 第一组 题号2,题目 45+32  与题号3的题目重复,题号3为 32+45
(2)第二组  题号5,题目 3+(2+1) 与题号7的题目重复,题号7为 1+2+3
 
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
二、需求分析
基于分析,我们可以知道该程序主要需要实现以下几点功能:
1、可以根据接受的参数自动生成批量的四则运算题目,题目中运算符主要包括‘+’,‘-’,‘*’,‘÷’,数值可以包含真分数,带分数,以及整数,可以将题目保存到相应的Exercises.txt中。
2、可以查找重复的题目,并在Grade.txt中标注。
3、可以根据生成的题目自动计算答案,并保存到相应的Answers.txt中。
4、可以根据用户输入的答案,自动批改,并将成绩保存至Grade.txt中。
 
三、功能设计
1、可以根据输入的参数自动生成题目,并且保存到Exercises.txt中
2、可以查找重复的题目,并在Grade.txt中标注。
3、可以根据生成的题目自动计算答案,并保存到相应的Answers.txt中。
4、可以根据用户输入的答案,自动批改,并将成绩保存至Grade.txt中。
 
四、设计实现
Java项目的结构如下:
 

各类的功能说明如下:

1、Test.java
该类是程序运行入口类,主要功能为调用其他功能模块按要求完成出题,批改,查重等过程

2、DataOper.java
该类主要完成整数与假分数,带分数之间的运算和比较以及格式化问题,并调用其他功能完成问题的查重与
作业的修改问题

3、Expression.java
该类主要用来保存表达式相关的信息,比如表达式的字符串表达与答案

4、GetExpression.java
该类调用其他功能模块,完成表达式的规范化,并且计算出表达式的值

5、Grade.java
该类用来保存成绩的相关信息

6、OperExpression.java
该类用来处理初步拼合而成的表达式,使其满足作业要求,并将初步的表达式统一规范化,便于以后的查重处理

7、ProExpression.java
该类主要用来产生初步拼合而成的表达式

 功能实现的基本过程:
 

查重思路的实现:

1、制定优化二叉树的策略,以达到将表达式规范化的目的,当表达式规范后,重复的表达式将以“同一面目”出现,我们之后就可以直接根据字符串的比较判断两个表达式是否相同。

优化策略如下:

(1)如果右子树的值大于左子树,则左右左子树互换,这样可以保证运算中不会出现负数。

(2)不管左右子树的运算符优先级如何,凡左右子树的根节点为运算符的,在生成表达式时统一在两边加上括号。

 

如图所示的二叉树,将生成:(1+3)-(1*2)

如:1+2+3,3+(1+2)的两个表达式,规范化后都会变为(1+2)+3,此时我们只需进行字符串比较即可判断题目是否重复

 代码说明:

1、

/*
     * 按规则优化二叉树,使其计算过程中不出现负数,并且中序遍历,生成查重使用的表达式,进行查重处理
     * 按照二叉树求值
     * 
     */
    public BinaryTree adjustmentTree(BinaryTree t) {
    	if(t!=null) {
    		String left_value =getTree_value(t.getLeft_tree());
        	String right_value = getTree_value(t.getRight_tree());
        	//若右子树的值大于左子树,则交换子树
        	if(right_value.equals("")||left_value.equals("")) {
        		return t;
        	}
        	else if(dataOper.operData(right_value, left_value,"?").equals("1")) {
        		t=swapChildTree(t);   		
        	}
        	t.setLeft_tree(adjustmentTree(t.getLeft_tree()));
        	t.setRight_tree(adjustmentTree(t.getRight_tree()));    
    	}
    		
    	return t;
    }
    //中序遍历二叉树,转化为符合预定运算顺序的带括号的表达式
    public String treeToExpression(BinaryTree t) {
    	String left_s="";
    	String right_s="";
    	String expression="";
    	if(t!=null) {
    		if(t.getLeft_tree()!=null&&t.getRight_tree()!=null) {
    			String left_root=t.getLeft_tree().getRoot_data();
    			String right_root=t.getRight_tree().getRoot_data();
    			if(left_root.equals("+")||left_root.equals("-")||left_root.equals("*")||left_root.equals("÷")) {
    				left_s="("+treeToExpression(t.getLeft_tree())+")";
    			}
    			else {
    				left_s=treeToExpression(t.getLeft_tree());
    			}
    			if(right_root.equals("+")||right_root.equals("-")||right_root.equals("*")||right_root.equals("÷")) {
    				right_s="("+treeToExpression(t.getRight_tree())+")";
    			}
    			else {
    				right_s=treeToExpression(t.getRight_tree());
    			}
    			
    		}
    		
    		expression=left_s + t.getRoot_data() + right_s;
    	}    	
    	return expression;
    	
    }

 2、

 

//由随机生成的基本表达式的后缀表达式按要求构建二叉树
    public BinaryTree OptiExpression(String expression) {
    	Stack<String> stack = new Stack<String>();
    	Stack<BinaryTree> tstack = new Stack<BinaryTree>();
    	BinaryTree bt = new BinaryTree();
    	char []express = expression.toCharArray();    	
    	String s = "";//暂存入栈信息
    	for(int i=0;i<express.length;i++) {
    		if(express[i]!='+'&&express[i]!='-'&&express[i]!='*'&&express[i]!='÷'&&express[i]!='#') {
    			s+=express[i];
    		}
    		else if(express[i]=='#'){
    			if(!s.equals("")) {
    				BinaryTree t = new BinaryTree();
        			t.setRoot_data(s);
        			t.setLeft_tree(null);
        			t.setRight_tree(null);
        			tstack.push(t);
        			bt=t;
    			}    	
    			s="";
    		}
    		else if(express[i]=='+'||express[i]=='-'||express[i]=='*'||express[i]=='÷') {
    			if(!s.equals("")) {
    				BinaryTree t = new BinaryTree();
        			t.setRoot_data(s);
        			t.setLeft_tree(null);
        			t.setRight_tree(null);
        			tstack.push(t);
        			bt=t;
    			}    			
    			s=""+express[i];
    			//System.out.println("zan"+tstack.size());
    			BinaryTree t1 = new BinaryTree();
    			t1.setRoot_data(s);    			
    			t1.setRight_tree(tstack.pop());
    			t1.setLeft_tree(tstack.pop());
    			tstack.push(t1);
    			s="";
    			bt=t1;
    		}
    	}
    	return bt;
    }

五、测试运行

 

 

 

 

 

六、PSD统计:

PSP2.1

Personal Software Process Stages

Time Senior Student

Time

 

Planning

计划

45

60

 

· Estimate

估计这个任务需要多少时间

800

900

 

Development

开发

600

650

 

· Analysis

需求分析 (包括学习新技术)

20

15

 

· Design Spec

生成设计文档

30

40

 

· Design Review

设计复审

10

10

 

· Coding Standard

代码规范

10

10

 

· Design

具体设计

20

15

 

· Coding

具体编码

600

800

 

· Code Review

代码复审

10

10

 

· Test

测试(自我测试,修改代码,提交修改)

20

30

 

Reporting

报告

30

60

 

·

测试报告

10

10

 

·

计算工作量

10

10

 

·

并提出过程改进计划

5

5

 

 

 

 

 

 

 

 七、小结

        由于最近开发任务有点多,所以一直到近两天才开始研究这个作业。之前看过一些二叉树实现查重的思路,但是我觉得太繁杂了,于是后来就想到我以上描述的那种方法进行查重。虽然括号会显得过多和冗余了,但是对于题目的查重却很容易实现,个人觉得这个方法还是不错的。这个题目看似其貌不扬,然后做起来却发现要处理的小问题非常的多,比如当数值如真分数,带分数和整数以字符串形式出现时,要想办法完成他们之间的计算,大小比较,还有格式互换等功能,工作真的很繁重。另外中缀表达式转为后缀表达式也有一定的难度,二叉树的生成和优化,以及利用二叉树对表达式进行求值时,用到了大量的递归思维,bug很多,调试了很久,才达到自己想要的目的。总的来说,这样一个小小的项目,收获也还是蛮多的,期待接下来将其改进,做得更好一些。

八、源代码

 源代码已提交至码云仓库:https://gitee.com/zhanglingchao/FourOperation