一、Github项目地址(合作人:林泽鸿3118005009、陈宇3118004999)
https://github.com/linzworld/arithmetic-generate
二、项目功能介绍
-
使用 -n 参数控制生成题目的个数【完成】
-
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围【完成】
-
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。【完成】
-
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。【完成】
-
每道题目中出现的运算符个数不超过3个。【完成】
-
程序一次运行生成的题目不能重复。【未完成】
-
生成的题目存入执行程序的当前目录下的Exercises.txt文件。【完成】
-
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。【完成】
-
程序应能支持一万道题目的生成。【完成】
-
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。【完成】
三、思路介绍
-
使用二叉树进行构建表达式,包括生成括号
生成四则表达式时,通常的是带有括号的表达式,我们这个项目用的是二叉树来进行运算,因为之前在四则运算中都是可以看成两个元素互相作用,加减乘除都是二元运算符,用二叉树比较合理。
-
全部采用分数的形式进行存储。
-
使用二叉树的结构进行是否重复的检验
a.当两个二叉树的结构一致时,则两个表达式重复了。
b.若判断的运算符是+或者*这种可以交换左右子表达式之后结果不变的,这时候就得通过比较第一个二叉树每个节点的左子树和第二个二叉树的右子树是否也相同,第一个二叉树每个节点的右子树和第二个二叉树的左子树是否也相同。不相同则返回不重复。
-
负数的处理
项目要求结果不能出现负数,而进行运算的分式都是整数或者0,不是负数。所以负数的情况只有在小的正数减去大的正数时出现,此时则将左右子树进行互换,变成大减去小,而且这个时候是处于生成表达式的时候,没有其他影响。
-
n/0的处理
每个表达式都设置一个是否舍弃当前表达式的标志,若出现n/0的情况,则将n/0中的0设置为1,防止其出现异常。并且将表达式设置为舍弃,后续的获取表达式时,则不放入HashMap中。
-
使用HashMap来与队友进行合作沟通。
HashMap中存放所有的表达式和答案,分工合作,一人负责生成表达式并且计算结果,另外一个人负责将得到的答案和实际作答进行对比和写入文件。
四、具体实现代码
主要类
-Expresssion.java
负责生成表达式和构建二叉树
-Fraction.java
分数类,进行存储每个分式的分子和分母,以便后续的带分数转化
-GenerateUtils.java
生成随机数和结果集合的工具类
-Node.java
存放操作数的节点
-OperatorNode.java
存放运算符的节点,该节点是Node的子类
-FileUtils.java
进行io的工具类
-Main.java
main函数
代码介绍
Expression
package com.lzh; /** * @Description 四则运算表达式的生成和构建二叉树 * @Author 林泽鸿 * @Date 2020/4/9 17:47 */ public class Expression { private static final String ADD = "+"; private static final String SUBTRACT = "-"; private static final String MULTIPLY = "×"; private static final String DIVIDE = "÷"; private static final String LEFT_BRACKETS = "("; private static final String RIGHT_BRACKETS = ")"; //运算符的种类 private static final String[] OPERATORS = {ADD, SUBTRACT, MULTIPLY, DIVIDE}; //根节点 private Node root; //出现n除以0的情况 private boolean isDivideForZero = false; public boolean isDivideForZero() { return isDivideForZero; } public void setDivideForZero(boolean divideForZero) { isDivideForZero = divideForZero; } //生成答案的范围 public static int range; public Node getRoot() { return root; } public void setRoot(Node root) { this.root = root; } //生成表达式 public Expression(int operator_number, int answer_range) { if (operator_number < 1) { throw new RuntimeException("运算符个数必须大于0"); } if (answer_range < 1) { throw new RuntimeException("运算结果范围必须大于等于1"); } this.range = answer_range; if (operator_number == 1) { root = generateNode(operator_number); } else { root = generateNode(GenerateUtils.getRandomInRange(operator_number) + 1); } }
/** * 构建生成四则运算表达式的二叉树 * @Param number 运算符的个数 * @Return com.lzh.Node 二叉树的头节点 * @Author 林泽鸿 * @Date 2020/4/11 17:41 **/ public Node generateNode(int number) { //如果是0就构造叶子节点 if (number == 0) { return new Node(Fraction.generateFraction(), null, null, 1); } //其他都是构造符号节点 OperatorNode parent = new OperatorNode(null, null, OPERATORS[GenerateUtils.getRandomInRange(4)]); int left = GenerateUtils.getRandomInRange(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()) { Node tmp = parent.left; parent.left = parent.right; parent.right = tmp; } parent.result = result; //计算树高 parent.high = Math.max(parent.left.high, parent.right.high) + 1; return parent; } //进行两个元素的计算 private Fraction calculate(String operator, Fraction leftFraction, Fraction rightFraction) { switch (operator) { case ADD: return leftFraction.add(rightFraction); case SUBTRACT: return leftFraction.subtract(rightFraction); case MULTIPLY: return leftFraction.multiply(rightFraction); //可能会出现除以0的情况,即rightFraction可能为0 case DIVIDE: if (rightFraction.getA() == 0) { this.isDivideForZero = true; rightFraction.setA(1); } return leftFraction.divide(rightFraction); default: throw new RuntimeException("该操作符不存在"); } } //打印出中缀表达式,包括括号 @Override public String toString() { return print(root); } /** * 中序遍历二叉树,左中右 * * @Param localRootNode 当前所在的最高节点,可以不是根节点 * @Return java.lang.String * @Author 林泽鸿 * @Date 2020/4/11 17:58 **/ private String print(Node localRootNode) { if (localRootNode == null) { return ""; } String left = print(localRootNode.left); String mid = localRootNode.toString(); //需要加括号的情况,一个节点的操作符为乘除,其子节点的操作符是加减 if (localRootNode.left instanceof OperatorNode && localRootNode instanceof OperatorNode) { if (leftBrackets(((OperatorNode) localRootNode.left).operator, ((OperatorNode) localRootNode).operator)) { left = LEFT_BRACKETS + " " + left + " " + RIGHT_BRACKETS; } } String right = print(localRootNode.right); if (localRootNode.right instanceof OperatorNode && localRootNode instanceof OperatorNode) { if (rightBrackets(((OperatorNode) localRootNode.right).operator, ((OperatorNode) 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 * @Author 林泽鸿 * @Date 2020/4/11 18:10 **/ private boolean isAddOrSubtract(String operator) { return operator.equals(ADD) || operator.equals(SUBTRACT); } /** * 是乘除运算符 * * @Param operator * @Return boolean * @Author 林泽鸿 * @Date 2020/4/11 18:10 **/ private boolean isMultiplyOrDivide(String operator) { return operator.equals(MULTIPLY) || operator.equals(DIVIDE); }
Fraction
package com.lzh; import com.cy.www.numberUtils; /** * @Description 计算数(分数),统一是叶子节点 * @Author 林泽鸿 * @Date 2020/4/9 22:20 */ public class Fraction { //分子 private int a; //分母,不能为0,默认为1 private int b = 1; public int getA() { return a; } public void setA(int a) { this.a = a; } public int getB() { return b; } public void setB(int b) { this.b = b; } //设置分子和分母 public Fraction(int a, int b) { setAB(a, b); } //通过表达式得到分子和分母,都未经过简化,分母可能为0 public Fraction(String result) { result.trim(); int a_index = result.indexOf("/"); int a1_index = result.indexOf("'"); //不是分式的时候 if (a_index == -1) { a = Integer.valueOf(result); } //是分式的时候 else { //分母 b = Integer.valueOf(result.substring(a_index + 1)); //真分数 if (a1_index == -1) { a = Integer.valueOf(result.substring(0, a_index)); } //带分数 else { int a1 = Integer.valueOf(result.substring(0, a1_index)); int a0 = Integer.valueOf(result.substring(a1_index + 1, a_index)); a = a1 * b + a0; } } setAB(a, b); } //将分子分母调整之后,存储到成员变量中 public void setAB(int a, int b) { if (b == 0) throw new RuntimeException("分母不能为0"); //结果默认是正数 int isNagitiveAB = 1; //调整符号,b只能为正数 if (a * b < 0) { isNagitiveAB = -1; } a = Math.abs(a); b = Math.abs(b); //最大公因数 int g = gcd(a, b); //化简 this.a = a * isNagitiveAB / g; this.b = b / g; } /** * 生成一个计算数 * * @Return java.lang.String * @Author 林泽鸿 * @Date 2020/4/9 23:35 **/ public static Fraction generateFraction() { //a.b 都是大于等于0的 int a = GenerateUtils.getRandomInRange(Expression.range); int b = GenerateUtils.getRandomInRange(Expression.range); //分母为0 while (b == 0) { b = GenerateUtils.getRandomInRange(Expression.range); } Fraction result = new Fraction(a, b); return result; } //加法 public Fraction add(Fraction right) { // a/b+c/d =(ad+bc)/bd return new Fraction( this.a * right.b + this.b * right.a, this.b * right.b ); } //减法 public Fraction subtract(Fraction right) { // a/b-c/d =(ad-bc)/bd return new Fraction( this.a * right.b - this.b * right.a, this.b * right.b ); } //乘法 public Fraction multiply(Fraction right) { // a/b * c/d = ac / bd return new Fraction( this.a * right.a, this.b * right.b ); } //乘法 public Fraction divide(Fraction right) { // a/b / c/d = ad / bc return new Fraction( this.a * right.b, this.b * right.a ); } // //求最大公因数,辗转相除法 private int gcd(int a, int b) { int big = a; if (big == 0) return 1; int small = b; //让a成为最大的 if (a < b) { big = b; small = a; } int mod = big % small; return mod == 0 ? small : gcd(small, mod); } //看当前分数是否为负数 boolean isNegative() { //结果默认是正数 boolean isNagitiveAB = false; if (a * b < 0) { isNagitiveAB = true; } return isNagitiveAB; } //将a,b转化为表达式 @Override public String toString() { //不是分式 if (b == 1) return String.valueOf(a); //真分式 else { int i = a / b; //余数 int j = a % b; if (i != 0) { return String.format("%d'%d/%d", i, j, b); } else { return String.format("%d/%d", a, b); } } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Fraction fraction = (Fraction) o; if (a != fraction.a) return false; return b == fraction.b; } //根据分子和分母 @Override public int hashCode() { int result = 31 * a + b; return result; } }
GenerateUtils
package com.lzh; import java.io.BufferedWriter; import java.io.FileWriter; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; /** * @Description 生成四则表达式 * @Author 林泽鸿 * @Date 2020/4/7 18:31 */ public class GenerateUtils { /** * 获得范围内的随机整数 * * @Param range 范围 * @Return int 随机数 * @Author 林泽鸿 * @Date 2020/4/9 22:11 **/ public static int getRandomInRange(int range) { ThreadLocalRandom random = ThreadLocalRandom.current(); return random.nextInt(range); } //生成题目和答案的映射关系 public static HashMap<String, String> generateMap(int exam_number, int answer_range) { if (exam_number < 1) { throw new RuntimeException("生成题目的个数必须大于0"); } if (answer_range < 1) { throw new RuntimeException("运算结果范围必须大于等于1"); } HashMap<String, String> hashMap = new HashMap<>(); for (int i = 1; hashMap.size() < exam_number; ) { //因为在运算的过程中会出现n÷0的情况,这时候就会抛异常 Expression expression = new Expression(3, answer_range); if ((hashMap.get(expression.toString()) != null || !"".equals(expression.toString())) && !expression.isDivideForZero()) { hashMap.put(expression.toString(), expression.getRoot().result.toString()); i++; } } return hashMap; } }
Node
package com.lzh; /** * @Description 定义一个存放操作数(分数)的节点 * @Author 林泽鸿 * @Date 2020/4/9 21:21 */ public class Node { //存储当前节点以下的计算结果 public Fraction result; public Node left; public Node right; public int high; public Node() { } public Node(Fraction result, Node left, Node 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; Node node = (Node) 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; } }
OpercatorNode
package com.lzh; /** * @Description 一个存放运算符的节点 * @Author 林泽鸿 * @Date 2020/4/9 21:22 */ public class OperatorNode extends Node { //运算符 public String operator; public OperatorNode(Node left, Node right, String operator) { //父类中无用的常量设置为null super(null, left, right, 0); this.operator = operator; } //中间节点存放运算符,需空格隔开 @Override public String toString() { return " " + operator + " "; } } 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++; } } //对比练习和答案,并将结果写入文件 public static void compare(File answerFile,File exerciseFile) throws IOException { if (!exerciseFile.exists()) { System.out.println("练习答案文件不存在"); return; } if (!answerFile.exists()) { System.out.println("答案文件不存在"); return; } //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]); } //存储标准答案 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()+"("); for (int str: rightRsult) { printWriter.print(str+","); } printWriter.println(")"); printWriter.print("Wrong:错误题数:"+errorRsult.size()+"("); for (int str: errorRsult) { printWriter.print(str+","); } printWriter.print(")"); printWriter.flush(); fileWriter.flush(); printWriter.close(); fileWriter.close(); } }