结对项目
结对项目
软件工程 | 班级网址 |
---|---|
作业要求 | 作业要求代码 |
作业目标 | 积累结对项目经验,提高交流和合作能力 |
合作者
黄博晓 信息安全 (1) 班 学号 : 3118005363
凌文宇 信息安全 (1) 班 学号 : 3118005372
作业代码链接
github 网址 : https://github.com/bxxiao/bxxiao/tree/master/ArithmeticGeneratorProject
PSP 表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 40 |
Development | 开发 | 740 | 790 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 150 |
· Design Spec | · 生成设计文档 | 50 | 40 |
· Design Review | · 设计复审 | 20 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 40 | 60 |
· Coding | · 具体编码 | 300 | 350 |
· Code Review | · 代码复审 | 40 | 50 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | 70 | 85 |
· Test Repor | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工作量 | 15 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 25 | 35 |
· 合计 | 840 | 915 |
1. 设计实现过程
包结构:
-
expression包:一个Expression对象表示一个四则表达式,其中提供了随机创建表达式、检验表达式、获取表达式运算结果和返回表达式字符串形式等方法。一个四则表达式封装为一个二叉树,TreeNode是二叉树的结点,每个结点保存一个运算符或运算数。
-
util包:
-
Calculator:主要提供了针对整数、分数的加减乘除运算、以及获取分数的最简形式的静态方法。运算结果以字符串形式返回。返回形式有整数和分数(如1/3,2'5/6)形式。
-
Ran:用于生成随机值。包括随机生成运算符、随机生成运算数(整数和分数)以及随机生成运算符在二叉树中的位置。
-
CommandResolver:命令解析器。解析main方法的args参数,获取其中的有用参数。若输入形式不符合要求,会打印提示信息。若输入形式符合要求,则获取其中指定的题目数和最大值并保存。
-
FilteUtil:用于将生成的题目跟答案写入文件。写入的格式如下:
1. exp1 = or answer1 2. exp2 = or answer2 3. exp3 = or answer3 ...
-
-
generator包:
- Generator:提供了一个
generate()
方法,使用CommandResolver
解析输入的命令,获取题目数和最大值,根据这两个值创建表达式和对应的答案,并使用FilteUtil
将题目和答案写入当前目录下的expFile.txt和answers.txt文件(若不存在会创建)。在创建每个表达式之前,先随机生成一个1-3的整数,作为运算符个数。 - Main:包含main方法。
- Generator:提供了一个
-
test包:包含一个测试类。(使用Junit)
2. 代码说明
2.1 表达式的字符串形式
一个表达式对应的二叉树是一个完全二叉树,其中,叶子结点都是运算数,父结点都是运算符。对这样的二叉树进行中序遍历可得到一个运算表达式,且每次返回父结点都会带上括号,如:
遍历结果是((1+2)-(3x4))
。
再通过以下原则去除多余的括号
假设待去括号的表达式为
(m1 op1 n1) op (m2 op2 n2)
这里m1、n1、m2、m2可能自身就是个表达式,也可能是数字,op、op1、op2为运算符1、若op为'÷',则 (m2 op2 n2)的括号必须保留;
2、若op为'x'或'-',如果op2为'+'或'-',则(m2 op2 n2)的括号必须保留;
3、若op为'x'或'÷',如果op1为'+'或'-',则(m1 op1 n1)的括号必须保留;
4、 除此之外,去掉括号不影响表达式的计算顺序。
如上面例子去掉多余括号后变为:1+2-3x4
将当前二叉树按以上规则转换为字符串形式的方法在TreeNode中:
public String toString(){
String leftExp = "";//左表达式
String rightExp = "";//右表达式
String result = "";//运算符
//若当前结点有孩子
if(hasChild()){
//右子树如果有孩子,说明右子树是一个表达式,而不是数字节点。
if(getRchild().hasChild()){
//判断左邻括号的运算符是否为'/'
if(str.equals("÷")){
//获取右子树的表达式,保留括号
rightExp = getRchild().toString();
}
//判断左邻括号的运算符是否为'x'或'-'
else if(str.equals("x") || str.equals("-")){
//判断op是否为'+'或'-'
if(getRchild().str.equals("+") || getRchild().str.equals("-")){
rightExp = getRchild().toString();
}
else{
//获取右子树的表达式,并且去括号
rightExp = getRchild().toString().substring(1, getRchild().toString().length()-1);
}
}
else{
//右子树除此之外都是可以去括号的。
rightExp = getRchild().toString().substring(1, getRchild().toString().length()-1);
}
}
else{
rightExp = getRchild().str;
}
//左子树的情况同右子树类似
if(getLchild().hasChild()){
if(str.equals("x") || str.equals("÷")){
if(getLchild().str.equals("+") || getLchild().str.equals("-")){
leftExp = getLchild().toString();
}
else{
leftExp = getLchild().toString().substring(1, getLchild().toString().length()-1);
}
}
else{
leftExp = getLchild().toString().substring(1, getLchild().toString().length()-1);
}
}
else{
leftExp = getLchild().str;
}
//获取当前的运算式,并加上括号
result = "(" + leftExp + " " + this.str + " " + rightExp + ")";
}
else{
//若没有孩子,说明是数字节点,直接返回数字
result = this.str;
}
return result;
}
2.2 随机生成表达式
2.2.1 随机创建表达式
随机生成二叉树的思路:
- 先随机生成一个运算符作为根结点。若只有一个运算符,直接生成两个运算数作为根节点的左右孩子。
- 若有2或3个运算符:
- 先根据运算符个数随机获取运算符结点位置的boolean数组(见下文
getChildPlace()
)。 - 遍历该boolean数组,若为true,则要创建一个表达式子树(运算符为父结点,运算数为其左右孩子);若为false,则创建一个运算数子树(只有一个结点)。
- 在上述遍历中设置一个遍历索引
i
,若i为偶数,则创建的子树作为"当前结点"的左孩子,否则为右孩子。且每创建一个作为左孩子的表达式子树,则该子树的根节点作为“当前结点”(当前结点从第一步生成的根节点开始)... - 这种方式创建子树能保证最后生成的二叉树是完全二叉树。
- 先根据运算符个数随机获取运算符结点位置的boolean数组(见下文
- 创建完表达式,调用
CalAndVal()
对表达式校验。
public void createExpression(){
TreeNode lchild, rchild, lnode, rnode;
//只有一个运算符
if(opCounts == 1){
lchild = new TreeNode(Ran.getNumber(max), null, null);
rchild = new TreeNode(Ran.getNumber(max), null, null);
root = new TreeNode(Ran.getOperator(), lchild, rchild);
}
else{
int num1 = 0;
boolean[] place = Ran.getChildPlace(opCounts);
root = new TreeNode(Ran.getOperator(), null, null);
opeList.add(root);
for(int i = 0; i < place.length; i++){
//place[i]为true,生成一个运算符,并为该运算符生成两个左右孩子运算数
if(place[i]){
lnode = new TreeNode(Ran.getNumber(max), null, null);
rnode = new TreeNode(Ran.getNumber(max), null, null);
//i为偶数(从0开始的),将新创建的运算符作为当前结点(运算符)的左孩子;为奇,则作为右孩子
if(i%2 == 0){
lchild = new TreeNode(Ran.getOperator(), lnode, rnode);
opeList.add(lchild);
opeList.get(num1).setLchild(lchild);
}
else{
rchild = new TreeNode(Ran.getOperator(), lnode, rnode);
opeList.add(rchild);
opeList.get(num1).setRchild(rchild);
}
}
//place[i]为false,则只生成一个运算数,组装到当前运算符的左右孩子
else{
if(i%2 == 0){
lchild = new TreeNode(Ran.getNumber(max), null, null);
opeList.get(num1).setLchild(lchild);
}
else{
rchild = new TreeNode(Ran.getNumber(max), null, null);
opeList.get(num1).setRchild(rchild);
}
}
//num1为偶数,说明当前结点还未生成有孩子,num1不变;若是奇数,则+1
num1 = num1 + i%2;
}
}
CalAndVal();//生成完二叉树进行校验
}
2.2.2 随机生成运算符结点位置
Ran类中包含一个根据运算符个数随机生成运算符结点位置的方法,其实现思路:
- 方法返回返回一个长度为2的boolean数组,true元素的个数表示要新创建的运算符个数。
- 在随机生成表达式的方法中会使用到该方法,true表示在当前结点中,创建一个以运算符为父结点,两个运算数为叶子结点的子二叉树,作为当前结点的左或右孩子;false则表示创建一个运算数,作为当前结点的左或右孩子。
- 若运算符个数为2,则需要再创建一个运算符,所以true,false各一个。
- 运算符个数为3,则需要再创建两个运算符。元素都是true。
public static boolean[] getChildPlace(int num){
boolean[] result = new boolean[]{true, true};
if(num==2){
Random random = new Random();
if(random.nextBoolean()){
result[0] = false;
}else {
result[1] = false;
}
}
return result;
}
2.2.3 校验表达式
校验表达式包括对除数为0,减法运算结果为负的情况的处理。
- 若除数为0,替换除号为其他运算符。
- 若减法结果为负,交换被减数和减数的位置。
CalAndVal()
实际调用了getResult()
方法,该方法获取当前结点对应的运算结果并按以上规则校验。
public String getResult(){
//若有子节点,说明当前结点是运算符
if(hasChild()){
switch(str){
case "+":
return Calculator.add(getLchild().getResult(), getRchild().getResult());
case "-":
String subResult = Calculator.sub(getLchild().getResult(), getRchild().getResult());
//若返回值为-1,交换左右孩子
if(subResult.contains("-")){
swapChild();
return Calculator.sub(getLchild().getResult(), getRchild().getResult());
}
else {
return subResult;
}
case "x":
return Calculator.mul(getLchild().getResult(), getRchild().getResult());
case "÷":
String divResult = Calculator.division(getLchild().getResult(), getRchild().getResult());
//返回结果为-1,表示除数为0,替换运算符
if(divResult.contains("-")){
while(str.equals("÷")){
//将当前运算符转换为其他运算符
str = Ran.getOperator();
}
return this.getResult();
}
else {
return divResult;
}
}
}
//无子节点,说明当前结点是叶子结点,直接返回结点值
return str;
}
3. 测试
输入命令-n 6 -r 10
(生成6道题目,最大值为10(不包括10)),生成的题目和答案如下:
题目:(在当前目录下的expFile.txt
中)
答案:(在当前目录下的answer.txt
中)
4. 小结
4.1 黄博晓
-
跟别人合作开发要注意沟通好。
-
数据结构很重要。
-
使用搜索引擎有时可以极大的提高效率...
4.2 凌文宇
-
基础真的很重要,以后要继续努力才能更好的和队友合作
-
全程大佬带飞,以后要继续加油,争取不拖后腿
-
事先的沟通与安排是必不可少的
参考链接
https://www.cnblogs.com/echoing/p/7878954.html
https://blog.csdn.net/kuku713/article/details/12684509