结对项目:自动生成小学四则运算题目
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 |
这个作业的目标 | 队友之间相互协作,实现一个自动生成小学四则运算题目的命令行程序 |
一、结对编程
1.项目成员
陈智超(3118005320),陈燕(3218005352)
2.Github项目地址
https://github.com/chenzhichaohh/ChenZC-ChenY-Pair-Project.git
3.commit记录
4. 实现的需求
4.1 使用 -n 参数控制生成题目的个数
4.2 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
4.3 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
4.4 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
4.5 每道题目中出现的运算符个数不超过3个。
4.6 程序一次运行生成的题目不能重复。
4.7 生成的题目存入执行程序的当前目录下的Exercises.txt文件。
4.8 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。
4.9 程序应能支持一万道题目的生成。
4.10 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。
4.psp
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 30 |
Development | 开发 | 960 | 1080 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 90 |
· Design Spec | · 生成设计文档 | 5 | 5 |
· Design Review | · 设计复审 | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 840 | 960 |
· Code Review | · 代码复审 | 60 | 90 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 60 | 90 |
· Test Repor | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
· 合计 | 1245 | 1425 |
二、程序设计
1.流程图
1.1项目执行流程
2.项目架构
2.1 工程架构
2.2 关键类的UML类图
3.效能分析
3.1 算法改进之前:
3.2 算法改进之后:
3.3 改进过程
可以看到,最耗费内存的就是生成题目的方法,第一次的算法,套用了两层循环,时间复杂度太高,借鉴快排的思想,达到条件之后马上跳出循环,这样时间复杂度就能够降低了。
4.算法逻辑
4.1随机数生成算法流程
4.2利用二叉树构造四则运算表达式并生成运算结果流程图
4.3去掉重复题目算法流程
5.设计思路(12个关键点)
5.1 采用二叉树的方式构建运算表达式,非叶子结点存储运算符,叶子结点存储数值。
5.2 运算符号:存储在一个枚举类中,包括加减乘除和括号。
5.3 运算数值:采用随机生成的方法。
5.4 分数的处理:因为java不存在可以处理分数运算的类,采用面向对象的思想,我们的处理方式是将整数与分数作为一个Fracton对象来处理,即整数就是分母为1的分数,即所有参与运算的数都可以看成是分数。
5.5 二叉树的详细说明:二叉树的节点分为两种,一种BiTreeNode(有一个Fraction类的result属性,存储当前节点以下的计算结果),存储参与运算的数字类,一种是OperatorCharNode(继承自BiTreeNode),存储运算符。
5.6 构建运算表达式: 存储在Expression类中,内含BitreeNode属性,指向一棵二叉树的根节点,中序遍历这棵二叉树,便可以转化成直观的运算表达式了,也就是中缀表达式。
5.7 括号的处理:需要加括号的情况,一个节点的操作符为乘除,其子节点的操作符是加减。
5.8 表达式的计算结果:Expression中有一个Fraction类的result属性,存储这个表达式的计算结果,result属性中含有BiTreeNode类的属性,用来存储这棵树的根节点,遍历这棵儿二叉树才能出结果。
5.9 负数的处理:由于参与运算的结果都是分数或整数,而且,分数的分子和分母都是自然数,因此,如果出现负数,则肯定是 “较小的数 – 较大的数“ 造成的。这里想到了两种处理方法,第一种是出现负数则抛异常,不要这道题目,继续循环生成题目。第二种是交换左右两棵子树,这样就变成了 “较大的数 – 减去较小的数”,就不会出现负数了,本项目采用第二种方法处理。
5.10 n/0的处理:每个表达式都设置一个是否舍弃当前表达式的标志,若出现n/0的情况,则将n/0中的0设置为1,防止其出现异常。并且将表达式设置为舍弃,后续的获取表达式时,则不放进生成的题目中。
5.11 判断重复的题目:由于乘法和加法满足交换律,因此,可以借助二叉树同构的方法,判断题目是否重复,如果非叶子节点是“*” 或者 “+”,则可以交换左右子树,如果能满足同构,则这两道题目是一样的。
5.12 采用Map的数据结构存储题目,key为表达式,value值为答案。
6.关键代码说明
6.1 运算符枚举类
package com.bichoncode.bean;
/**
* 操作符枚举类
* 包括 + - * / ( )
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/09
*/
public enum OperationalCharEnum {
PlUS("+"),
SUBTRACT("-"),
MULTIPLY("*"),
DIVIDE("/"),
LEFT_BRACKETS("("),
RIGHT_BRACKETS(")");
// 操作字符
private String valueChar;
OperationalCharEnum(String valueChar) {
this.valueChar = valueChar;
}
public String getValueChar() {
return valueChar;
}
public void setValueChar(String valueChar) {
this.valueChar = valueChar;
}
}
6.2 二叉树节点类
package com.bichoncode.bean;
/**
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/10
*/
public class BiTreeNode {
// 存储当前节点以下的计算结果
public Fraction result;
public BiTreeNode left;
public BiTreeNode right;
public int high;
public BiTreeNode() {
}
public BiTreeNode(Fraction result, BiTreeNode left, BiTreeNode right, int high) {
this.result = result;
this.left = left;
this.right = right;
this.high = high;
}
// 打印出表达式
@Override
public String toString() {
return result.toString();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass()) return false;
BiTreeNode node = (BiTreeNode) o;
if (result != null ? !result.equals(node.result) : node.result != null)
return false;
if (right != null ? !right.equals(node.right) : node.right != null)
return false;
return left != null ? left.equals(node.left) : node.left == null;
}
// 递归
@Override
public int hashCode() {
int result1 = result != null ? result.hashCode() : 0;
result1 = 31 * result1 + (right != null ? right.hashCode() : 0);
result1 = 31 * result1 + (left != null ? left.hashCode() : 0);
return result1;
}
}
6.3二叉树存储字符的节点类
package com.bichoncode.bean;
/**
* 操作运算符节点
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/10
*/
public class OperatorCharNode extends BiTreeNode {
// 运算符
public String operator;
public OperatorCharNode(BiTreeNode left, BiTreeNode right, String operator) {
// 父类中无用的常量设置为null
super(null, left, right, 0);
this.operator = operator;
}
// 中间节点存放运算符,按需求,用空格隔开
@Override
public String toString() {
return " " + operator + " ";
}
}
6.4 分数类
package com.bichoncode.bean;
import com.bichoncode.utils.RandomUtils;
/**
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/10
*/
public class Fraction {
// 分子
private int numerator;
// 分母,不能为0,默认为1
private int denominator = 1;
public int getNumerator() {
return numerator;
}
public void setNumerator(int numerator) {
this.numerator = numerator;
}
public int getDenominator() {
return denominator;
}
public void setDenominator(int denominator) {
this.denominator = denominator;
}
// 设置分子和分母
public Fraction(int numerator, int denominator) {
setFactionValue(numerator, denominator);
}
// 通过表达式得到分子和分母,都未经过简化,分母可能为0
public Fraction(String result) {
result.trim();
int numerator_index = result.indexOf("/");
int numerator1_index = result.indexOf("'");
// 不是分式的时候
if (numerator_index == -1) {
numerator = Integer.valueOf(result);
}
// 是分式的时候
else {
// 分母
denominator = Integer.valueOf(result.substring(numerator_index + 1));
// 真分数
if (numerator1_index == -1) {
numerator = Integer.valueOf(result.substring(0, numerator_index));
}
// 带分数
else {
int numerator1 = Integer.valueOf(result.substring(0, numerator1_index));
int numerator0 = Integer.valueOf(result.substring(numerator1_index + 1, numerator_index));
numerator = numerator1 * denominator + numerator0;
}
}
setFactionValue(numerator, denominator);
}
// 将分子分母调整之后,存储到成员变量中
public void setFactionValue(int numerator, int denominator) {
if (denominator == 0)
throw new RuntimeException("分母不能为0");
// 结果默认是正数
int isNagitiveAB = 1;
// 调整符号,b只能为正数
if (numerator * denominator < 0) {
isNagitiveAB = -1;
}
numerator = Math.abs(numerator);
denominator = Math.abs(denominator);
// 最大公因数
int g = gcd(numerator, denominator);
// 化简
this.numerator = numerator * isNagitiveAB / g;
this.denominator = denominator / g;
}
public static Fraction generateFraction() {
// 分子分母 都是大于等于0的
int numerator = RandomUtils.getARandom(Expression.range);
int denominator = RandomUtils.getARandom(Expression.range);
// 分母为0
while (denominator == 0) {
denominator = RandomUtils.getARandom(Expression.range);
}
Fraction result = new Fraction(numerator, denominator);
return result;
}
// 加法
public Fraction plus(Fraction right) {
// a/b+c/d =(ad+bc)/bd
return new Fraction(
this.numerator * right.denominator + this.denominator * right.numerator,
this.denominator * right.denominator
);
}
// 减法
public Fraction subtract(Fraction right) {
// a/b-c/d =(ad-bc)/bd
return new Fraction(
this.numerator * right.denominator - this.denominator * right.numerator,
this.denominator * right.denominator
);
}
// 乘法
public Fraction multiply(Fraction right) {
// a/b * c/d = ac / bd
return new Fraction(
this.numerator * right.numerator,
this.denominator * right.denominator
);
}
// 除法
public Fraction divide(Fraction right) {
// a/b / c/d = ad / bc
return new Fraction(
this.numerator * right.denominator,
this.denominator * right.numerator
);
}
// 辗转相除法,求最大公约数
private int gcd(int numerator, int denominator) {
int big = numerator;
if (big == 0)
return 1;
int small = denominator;
//让a成为最大的
if (numerator < denominator) {
big = denominator;
small = numerator;
}
int mod = big % small;
return mod == 0 ? small : gcd(small, mod);
}
// 看当前分数是否为负数
boolean isNegative() {
// 结果默认是正数
boolean isNagitiveFraction = false;
if (numerator * denominator < 0) {
isNagitiveFraction = true;
}
return isNagitiveFraction;
}
// 将分子分分母转化成手写形式
@Override
public String toString() {
//不是分式
if (denominator == 1)
return String.valueOf(numerator);
//真分式
else {
int i = numerator / denominator;
//余数
int j = numerator % denominator;
// 分母为0则直接返回0
if (numerator == 0) {
return String.format("%d", 0);
}
if (i != 0) {
return String.format("%d'%d/%d", i, j, denominator);
} else {
return String.format("%d/%d", numerator, denominator);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fraction fraction = (Fraction) o;
if (numerator != fraction.numerator) return false;
return denominator == fraction.denominator;
}
// 重写hashcode
@Override
public int hashCode() {
int result = 31 * numerator + denominator;
return result;
}
}
6.5四则运算表达式类
package com.bichoncode.bean;
import com.bichoncode.exception.CommonException;
import com.bichoncode.utils.RandomUtils;
/**
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/10
*/
public class Expression {
private String PLUS = OperationalCharEnum.PlUS.getValueChar();
private String SUBTRACT = OperationalCharEnum.SUBTRACT.getValueChar();
private String MULTIPLY = OperationalCharEnum.MULTIPLY.getValueChar();
private String DIVIDE = OperationalCharEnum.DIVIDE.getValueChar();
private String LEFT_BRACKETS = OperationalCharEnum.LEFT_BRACKETS.getValueChar();
private String RIGHT_BRACKETS = OperationalCharEnum.RIGHT_BRACKETS.getValueChar();
// 运算符的种类
private String[] OPERATORS = {PLUS, SUBTRACT, MULTIPLY, DIVIDE};
// 二叉树的根节点
private BiTreeNode root;
// 是否出现除数为0的情况
private boolean divisorIsZero = false;
public boolean divisorIsZero() {
return divisorIsZero;
}
public void setdivisorIsZero(boolean divisorIsZero) {
divisorIsZero = divisorIsZero;
}
// 生成答案的范围
public static int range;
public BiTreeNode getRoot() {
return root;
}
public void setRoot(BiTreeNode root) {
this.root = root;
}
// 生成四则运算表达式
public Expression(int operator_number, int answer_range) {
if (operator_number < 1) {
throw new CommonException("运算符个数必须大于0");
}
if (answer_range < 1) {
throw new RuntimeException("运算结果范围必须大于等于1");
}
this.range = answer_range;
if (operator_number == 1) {
root = generateNode(operator_number);
} else {
root = generateNode(RandomUtils.getARandom(operator_number) + 1);
}
}
/**
* 构建生成四则运算表达式的二叉树
*
* @Param number 运算符的个数
**/
public BiTreeNode generateNode(int number) {
// 如果是0就构造叶子节点
if (number == 0) {
return new BiTreeNode(Fraction.generateFraction(), null, null, 1);
}
// 其他都是构造符号节点
OperatorCharNode parent = new OperatorCharNode(null, null, OPERATORS[RandomUtils.getARandom(4)]);
int left = RandomUtils.getARandom(number);
// 递归下去构造左孩子和右孩子
parent.left = generateNode(left);
// 总数要减去当前已经构建出来的这一个节点
parent.right = generateNode(number - 1 - left);
// 然后计算结果
Fraction result = calculate(parent.operator, parent.left.result, parent.right.result);
// 如果是负数,就是出现小的减去大的情况,这时候交换左右孩子
if (result.isNegative()) {
BiTreeNode tmp = parent.left;
parent.left = parent.right;
parent.right = tmp;
}
// 重新计算结果
parent.result = calculate(parent.operator, parent.left.result, parent.right.result);
// 计算树高
parent.high = Math.max(parent.left.high, parent.right.high) + 1;
return parent;
}
// 进行两个元素的计算
private Fraction calculate(String operator, Fraction leftFraction, Fraction rightFraction) {
if (operator.equals(PLUS))
return leftFraction.plus(rightFraction);
else if (operator.equals(SUBTRACT))
return leftFraction.subtract(rightFraction);
else if (operator.equals(MULTIPLY))
return leftFraction.multiply(rightFraction);
else if (operator.equals(DIVIDE)) {
//可能会出现除以0的情况,即rightFraction可能为0
if (rightFraction.getNumerator() == 0) {
this.divisorIsZero = true;
rightFraction.setNumerator(1);
}
return leftFraction.divide(rightFraction);
} else
throw new RuntimeException("该操作符不存在");
}
// 打印四则运算表达式
@Override
public String toString() {
return print(root);
}
/**
* 中序遍历二叉树,左中右
*
* @Param localRootNode 当前所在的最高节点,可以不是根节点
* @Return java.lang.String
**/
private String print(BiTreeNode localRootNode) {
if (localRootNode == null) {
return "";
}
String left = print(localRootNode.left);
String mid = localRootNode.toString();
// 需要加括号的情况,一个节点的操作符为乘除,其子节点的操作符是加减
if (localRootNode.left instanceof OperatorCharNode && localRootNode instanceof OperatorCharNode) {
if (leftBrackets(((OperatorCharNode) localRootNode.left).operator, ((OperatorCharNode) localRootNode).operator)) {
left = LEFT_BRACKETS + " " + left + " " + RIGHT_BRACKETS;
}
}
String right = print(localRootNode.right);
if (localRootNode.right instanceof OperatorCharNode && localRootNode instanceof OperatorCharNode) {
if (rightBrackets(((OperatorCharNode) localRootNode.right).operator, ((OperatorCharNode) localRootNode).operator)) {
right = LEFT_BRACKETS + " " + right + " " + RIGHT_BRACKETS;
}
}
return left + mid + right;
}
// 向左遍历时,需要括号
private boolean leftBrackets(String left, String mid) {
return (isAddOrSubtract(left) && isMultiplyOrDivide(mid));
}
// 向右遍历时,需要括号
private boolean rightBrackets(String right, String mid) {
//有可能出现2*3 /( 4*5 )的情况,所以不用加括号只有当
return !(isAddOrSubtract(mid) && isMultiplyOrDivide(right));
}
/**
* 是加减运算符
*
* @Param operator
* @Return boolean
**/
private boolean isAddOrSubtract(String operator) {
return operator.equals(PLUS) || operator.equals(SUBTRACT);
}
/**
* 是乘除运算符
*
* @Param operator
* @Return boolean
**/
private boolean isMultiplyOrDivide(String operator) {
return operator.equals(MULTIPLY) || operator.equals(DIVIDE);
}
}
6.6 异常处理类
package com.bichoncode.utils;
import com.bichoncode.bean.BiTreeNode;
/**
* 去重工具类
* @author ChenYan
*/
public class DistincOperatorUtils {
/**
* 使用递归判断两科二叉树是否同构
* @param root1
* @param root2
* @return
*/
public static boolean isomorphism(BiTreeNode root1, BiTreeNode root2) {
if (root1 == null && root2 == null)
return true;
if (root1 == null && root2 != null) {
return false;
}
if (root1 != null && root2 == null) {
return false;
}
if (!root1.result.equals(root2.result) )
return false;
return isomorphism(root1.left, root2.left) && isomorphism(root1.right, root2.right)
||isomorphism(root1.left, root2.right) && isomorphism(root1.right, root2.left);
}
}
6.7文件读写工具类
package com.bichoncode.utils;
import java.io.*;
import java.util.*;
/**
* @author ChenYan
* @create 2020/10/11
*/
public class FileUtils {
public static void writeTitle(PrintWriter printWriter, Map<String,String> map){
Set<String> titles=map.keySet();
int i=1;
for(String title:titles){
printWriter.println(i+":"+title);
i++;
}
}
// 将答案写入文件中
public static void writeAnswer(PrintWriter printWriter,Map<String,String> map){
Set<String> answer=map.keySet();
int i=1;
for (String key :answer){
String value=map.get(key);
printWriter.println(i+":"+value);
i++;
}
}
}
6.8随机数生成工具类
/**
* @author ChenYan
* @create 2020/10/10
*/
public class RandomUtils {
public static int getARandom(int range){
ThreadLocalRandom random = ThreadLocalRandom.current();
// 产生一个包括上限不包括下陷的值
return random.nextInt(range);
}
}
6.9答案处理工具类
package com.bichoncode.utils;
import com.bichoncode.bean.BiTreeNode;
import com.bichoncode.exception.CommonException;
import com.bichoncode.bean.Expression;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/11
*/
public class AnswerUtils {
// 生成题目和答案的映射关系
public static HashMap<String, String> generateMap(int exam_number, int answer_range) {
if (exam_number < 1) {
throw new CommonException("生成题目的个数必须大于0");
}
if (answer_range < 1) {
throw new CommonException("运算数字的范围必须大于等于1");
}
// 存储去重之后的题目
HashMap<String, String> hashMap = new HashMap<>();
// 存储刚生成的题目(此时未去重)
HashMap<Expression, String> hashMap2 = new HashMap<>();
while (hashMap.size() < exam_number) {
for (int i = 1; hashMap2.size() < exam_number + 2000; ) {
// 因为在运算的过程中会出现n/0的情况,这时候就会抛异常
Expression expression = new Expression(3, answer_range);
if ((hashMap.get(expression.toString()) != null || !"".equals(expression.toString()))
&&
!expression.divisorIsZero()) {
hashMap2.put(expression, expression.getRoot().result.toString());
i++;
}
}
// 去重
HashMap<Expression, String> distincMap = distinc(hashMap2);
hashMap2.clear();
hashMap2 = distincMap;
for (Expression expression : distincMap.keySet()) {
hashMap.put(expression.toString(), expression.getRoot().result.toString());
if (hashMap.size() == exam_number) {
break;
}
}
}
return hashMap;
}
// 比对答案和练习题
public static void compare(File answerFile, File exerciseFile) throws IOException {
if (!exerciseFile.exists()) {
throw new CommonException("练习答案文件不存在");
}
if (!answerFile.exists()) {
throw new CommonException("答案文件不存在");
}
// key是题号,value是答案
Map<Integer, String> exerciseMap = new HashMap<>();
Map<Integer, String> answerMap = new HashMap<>();
// 对比的结果
List<Integer> rightRsult=new LinkedList<>();
List<Integer> errorRsult=new LinkedList<>();
InputStreamReader exerciseIn = new InputStreamReader(new FileInputStream(exerciseFile.getAbsolutePath()), StandardCharsets.UTF_8);
InputStreamReader answerIn = new InputStreamReader(new FileInputStream(answerFile.getAbsolutePath()), StandardCharsets.UTF_8);
BufferedReader exerciseReader = new BufferedReader(exerciseIn);
BufferedReader answerReader = new BufferedReader(answerIn);
String string = null;
// 存储的练习答案
while ((string = exerciseReader.readLine()) != null) {
string = string.replaceAll(" +", "");
string = string.replaceAll("\uFEFF", "");
String TEXT=string.split("[:]")[0];
exerciseMap.put(Integer.valueOf(string.split("[:]")[0]), string.split(":")[1].split("=")[1]);
}
while ((string = answerReader.readLine()) != null) {
string = string.replaceAll(" +", "");
string = string.replaceAll("\uFEFF", "");
answerMap.put(Integer.valueOf(string.split("[:]")[0]), string.split(":")[1]);
}
exerciseReader.close();
answerReader.close();
// 比较答案
for (int i = 1; i <= answerMap.size(); i++){
if(exerciseMap.containsKey(i)){
if(exerciseMap.get(i).equals(answerMap.get(i))){
rightRsult.add(i);
}else {
errorRsult.add(i);
}
}
}
// 将比较结果存储到文件中
File file=new File("Grade.txt");
FileWriter fileWriter = new FileWriter(file, true);
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.println(" ");
printWriter.print("Correct:正确题数:"+rightRsult.size()+"(");
System.out.print("Correct:正确题数:"+rightRsult.size()+"(");
for (int str: rightRsult) {
printWriter.print(str+",");
System.out.print(str+",");
}
printWriter.println(")");
System.out.println(")");
printWriter.print("Wrong:错误题数:"+errorRsult.size()+"(");
System.out.print("Wrong:错误题数:"+errorRsult.size()+"(");
for (int str: errorRsult) {
printWriter.print(str+",");
System.out.print(str+",");
}
printWriter.print(")");
System.out.print(")");
printWriter.flush();
fileWriter.flush();
printWriter.close();
fileWriter.close();
System.out.println("比较完成,");
}
/**
* 去除重复的题目
* @param map
* @return
*/
public static HashMap<Expression, String> distinc(HashMap<Expression, String> map){
HashMap<Expression, String> distincMap = new HashMap<>();
List<Expression> repeatList = new ArrayList<>();
for (String key : map.values()) {
List<Expression> keyList = getRepratKeys(map, key);
if (keyList.size() > 1) {
// 获取两个表达式的根节点
BiTreeNode root1 = keyList.get(0).getRoot();
for (int i = 1; i < keyList.size(); i++){
BiTreeNode root2 = keyList.get(i).getRoot();
boolean isomorphism = DistincOperatorUtils.isomorphism(root1, root2);
if (isomorphism) {
// 如果题目重复,则移除第二道
//map.remove(keyList.get(i));
repeatList.add(keyList.get(i));
}
}
}
}
for (Expression expression : map.keySet()) {
if (!repeatList.contains(expression)) {
distincMap.put(expression, expression.getRoot().result.toString());
}
}
return distincMap;
}
public static List<Expression> getRepratKeys(Map<Expression, String> map, String value){
Set set = map.entrySet(); //通过entrySet()方法把map中的每个键值对变成对应成Set集合中的一个对象
Iterator<Map.Entry<Expression, String>> iterator = set.iterator();
ArrayList<Expression> arrayList = new ArrayList();
while(iterator.hasNext()){
//Map.Entry是一种类型,指向map中的一个键值对组成的对象
Map.Entry<Expression, String> entry = iterator.next();
if(entry.getValue().equals(value)){
arrayList.add(entry.getKey());
}
}
return arrayList;
}
}
6.10 main方法
package com.bichoncode;
import com.bichoncode.exception.CommonException;
import com.bichoncode.utils.FileUtils;
import com.bichoncode.utils.AnswerUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Scanner;
/**
* @author BichonCode
* @mail chenzhichaohh@163.com
* @create 2020/10/10
*/
public class Main {
public static void main(String[] args) throws IOException {
// 运算数字的范围
int range = 0;
// 题目数量
int number = 0;
System.out.println("1.生成题目请输入: -n 题目树龄 -r 题目数字的范围。例如:-n 5 -r 5");
System.out.println("2.对照答案请输入: -e:练习题的绝对路径 -a: 答案的绝对路径。例如: -e F:exercises.txt -a F:answerfile.txt");
System.out.println("请输入");
Scanner sc = new Scanner(System.in);
String string = sc.nextLine();
args = string.split("\\s+");
// 判断参数是否正确
if (args.length < 4) {
throw new CommonException("请检查参数是否正确");
}
// 获取参数
for (int i = 0; i < args.length; i++) {
if ("-n".equals(args[i])) {
number = Integer.parseInt(args[i + 1]);
i++;
} else if ("-r".equals(args[i])) {
range = Integer.parseInt(args[i + 1]);
i++;
} else {
break;
}
}
// 判断是否生成题目,如果不是生成题目,则是对照答案
// range == 0 && number == 0意味着没有输入这两个参数的值,则是对照答案
// 生成题目和答案: -n 5 -r 5
// 对照答案:-e <exercisefile>.txt -a <answerfile>.txt
if (range == 0 && number == 0) { // 对照答案 -e F:exercises.txt -a F:answerfile.txt
String answerFileName;
String execiseFileName;
execiseFileName = args[1];
answerFileName = args[3];
File answerFile = new File(answerFileName);
File exerciseFile = new File(execiseFileName);
AnswerUtils.compare(answerFile, exerciseFile);
} else {
// -n 5 -r 5
// 出题和生成答案
HashMap<String, String> map = AnswerUtils.generateMap(number, range);
File file = new File("F:exercises.txt");
//File file = new File(args[1]);
File answerFile = new File("F:answerfile.txt");
//File answerFile = new File(args[3]);
try {
// 将生成的题目写入文件中
FileWriter fileWriter = new FileWriter(file, true);
PrintWriter printWriter = new PrintWriter(fileWriter);
FileUtils.writeTitle(printWriter, map);
printWriter.flush();
fileWriter.flush();
printWriter.close();
fileWriter.close();
System.out.println("题目已生成,文件路径为F:exercises.txt");
} catch (IOException e) {
e.printStackTrace();
}
try {
// 将答案写入文件中
FileWriter fileWriter = new FileWriter(answerFile, true);
PrintWriter printWriter = new PrintWriter(fileWriter);
FileUtils.writeAnswer(printWriter, map);
printWriter.flush();
fileWriter.flush();
printWriter.close();
fileWriter.close();
System.out.println("答案已生成,文件路径为F:answerfile.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.运行结果
7.1 测试题目的生成
生成的练习题:
生成的答案:
7.2测试答案校对
提交的练习答案:
正确答案:
校对结果:
7.3 10000道题目的生成
三、项目小结
- 经过这个结对项目,我们不再只是局限于原先的个人项目的各自开发,而是作为一个团队来一块开发这个项目。结对编程让我们的工作更有动力,能够集思广益减少犯错误的几率,但是对两个人的工作有顺序和时间前后的约束,容易从双线程退化成单线程。
本次编程的分析和设计,是经历一起讨论之后,综合各自的想法,最终得出的一个设计,比自己考虑的更加周全详细。编码过程各自负责各自的模块,以及运用到git,大大提高了我们的工作的效率。测试方面,编写这部分的人负责做单元测试,然而其他成员也可以在这个基础上,进行一些黑盒测试。
总之,通过这次结对编程项目,不光锻炼巩固了面向对象开发的流程,还体验了在一个团队中进行交流、合作进行开发。 - 建议分享: 结对项目中,每个人需要负责不同的模块,需要协调好分工,但是又不能仅仅负责自己的模块,还需要看懂对方的代码,双方之间可以通过CodeReview的方式审核代码。