这次的任务是设计一个能生成四则运算的程序。
从程序的实现功能来看,程序可以自动生成指定数量的题目和对应的答案,可以通过设置参数来对生成的题目中的运算符个数和计算数的产生范围进行控制。
对给定的题目集合计算出对应的答案,以及对给定题目和其对应答案进行验证处理,对题目集合进行查重,得到重复题目信息。
需求分析:
1、自动生成四则运算的题目与答案,可用参数控制生成题目数量、运算符个数、计算数产生范围
2、计算分数的加减法 例如:3-2/3/2*3/5 = 14/5
3、对题目进行查重,得到重复信息
4、对给定的题目进行计算得到,得到答案集合
5、对给定的题目和答案进行验证
对此,我设计一个类MathFactory.java来专门负责,处理以上问题。

package factory; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * 四则运算工厂类 * * @author xumz * */ public class MathFactory { private int radius;// 题目中数值的范围,不包括上限 private int operatorNum;// 运算符个数 private int questionNum;// 运算符个数 private String question;// 单个题目 private String answer;// 单个答案 private List<String> questionsList;// 题目集合 private List<String> answersList;// 答案集合 private List<String> mathList;// 题目加答案集合 private List<List<Integer>> number;// 所有计算数的集合 private List<List<Character>> opeator;// 所有运算符的集合 /** * 生产一道计算题 * * @return String */ String productSingleMath() { question = createEasyMathQuestion(); answer = createMathAnswer(); return question + "=" + answer; } /** * 生产计算题集合 * * @return List */ List<String> productMathQuestionList() { questionsList = new ArrayList<String>(); for (int i = 0; i < questionNum; i++) {// 设置题数 questionsList.add(createEasyMathQuestion()); } return questionsList; } /** * 生成简单的数学题目,整数运算 * * @return String */ String createEasyMathQuestion() { Random rand = new Random(); String str1 = "" + rand.nextInt(radius);// 设置计算值范围 String tempNum = null;// 随机生成的计算数 String tempOprator = null;// 随机生成的运算符 // 注意:'/'后面不能跟0 for (int j = 0; j < operatorNum; j++) {// 这里设置操作符个数 tempOprator = getOpeator(rand.nextInt(4));// 给你个魔鬼数字,自己看下面方法 if (tempOprator.equals("/")) { tempNum = "" + (rand.nextInt(radius) + 1);// '/'后面不能跟0 } else { tempNum = "" + rand.nextInt(radius); } str1 += tempOprator + tempNum; } question = str1; return str1; } /** * 获取运算符 * * @param key * @return */ static String getOpeator(int key) { String opeator = null; switch (key) { case 0: opeator = "+"; break; case 1: opeator = "-"; break; case 2: opeator = "*"; break; case 3: opeator = "/"; break; default: System.out.println("参数输入发生错误"); } return opeator; } /** * 成答案集合 * * @return */ List<String> productMathAnswerList() { answersList = new ArrayList<String>(); opeator = new ArrayList<List<Character>>(); number = new ArrayList<List<Integer>>(); for (int i = 0; i < questionsList.size(); i++) { question = questionsList.get(i); answersList.add(createMathAnswer()); } return answersList; } // * 思路:将question中的String题目,拿过来,拆分成计算数和运算符,将计算数进行类型转换成可计算的数字 // 运算符转换成char类型,判断优先级 /** * 生成答案 * * @return String */ String createMathAnswer() { char[] tempStringtoChar = question.toCharArray();// 暂时存放String转换成的字符 List<Character> opeatorList = new ArrayList<Character>();// 存放运算符 for (int i = 0; i < tempStringtoChar.length; i++) { switch (tempStringtoChar[i]) { case '+': opeatorList.add('+'); break; case '-': opeatorList.add('-'); break; case '*': opeatorList.add('*'); break; case '/': opeatorList.add('/'); break; default: break; } } List<Character> tempOpeatorList = new ArrayList<Character>();// 存放运算符备份 tempOpeatorList.addAll(opeatorList); opeator.add(tempOpeatorList); // 计算答案 // 思路:将运算符两边的计算数进行相应运算,将结果存赋值给第一个计算数,移除第二个计算数,最终只剩下一个计算结果 String[] tempStringNum = question.split("[+ * / -]");// 暂时存放String类型的计算数字 List<Integer> numList = new ArrayList<Integer>();// 存放计算数集合 for (int i = 0; i < tempStringNum.length; i++) { numList.add(Integer.parseInt(tempStringNum[i])); } List<Integer> tempNumList = new ArrayList<Integer>();// 存放计算数集合备份 tempNumList.addAll(numList); number.add(tempNumList); // 除法运算,转换成分数形式 // 思路: //1处理连续的乘除,将式子简化,如0+1+2/3/4+5/6*7+8 简化成0+1+2/12+35/6+8 //2、集中所有'/'后面的计算数divisor,求公倍数,作为所有计算数的分母denominator //3、消除 * / 使式子只剩下加减法运算 // 4、将所得数进行后面的加减法运算 // 5、最后添上最大公约数为分母,约分。 int denominator = 1;// 公倍数 boolean divFlag = false; if (opeatorList.contains('/')) { divFlag = true; // 将除数,化简成,连续除的只有一个除数。例如:1/2/3 =》 1/6 // 1/2/3*4 => 4/6 for (int i = 0; i < opeatorList.size(); i++) { if (opeatorList.get(i).equals('/')) { for (int j = i + 1; j < opeatorList.size(); j++) { if (opeatorList.get(j).equals('/')) { numList.set(j, numList.get(j) * numList.get(j + 1)); } else if (opeatorList.get(j).equals('*')) { numList.set(i, numList.get(j + 1) * numList.get(i)); } else { break; } opeatorList.remove(j); numList.remove(j + 1); j--; } } } // System.out.println("第一次化简" + numList); // System.out.println("第一次化简" + opeatorList); // 求最公倍数 for (int i = 0; i < opeatorList.size(); i++) { if (opeatorList.get(i) == '/') { denominator *= numList.get(i + 1); } } // System.out.println("公倍数:" + denominator); // 消除 *运算符 if (opeatorList.contains('*')) { for (int i = 0; i < opeatorList.size(); i++) { if (opeatorList.get(i) == '*') { numList.set(i, (numList.get(i) * numList.get(i + 1))); numList.remove(i + 1); opeatorList.remove(i); i--; } } } // System.out.println("消除*:" + numList); // System.out.println("消除*:" + opeatorList); // 给非乘除运算的数乘公倍数 例如:1+2/3+4 中的1和4 for (int i = 0; i < opeatorList.size(); i++) { // System.out.println("opeatorList进来:" + opeatorList.get(i)); boolean condition = opeatorList.get(i).equals('+') || opeatorList.get(i).equals('-'); // System.out.println(condition); if (condition) { if (i == 0) {// 首部特殊情况 numList.set(0, numList.get(0) * denominator); } else if (i == opeatorList.size() - 1) {// 尾部2个部特殊情况 numList.set(numList.size() - 1, numList.get(numList.size() - 1) * denominator); if (opeatorList.get(i - 1).equals('+') || opeatorList.get(i - 1).equals('-')) { numList.set(i, numList.get(i) * denominator); } } else { if (opeatorList.get(i - 1).equals('+') || opeatorList.get(i - 1).equals('-')) { numList.set(i, numList.get(i) * denominator); } } } } // System.out.println("给非乘除运算倍化" + numList); // System.out.println("给非乘除运算倍化" + opeatorList); // 消除 / 运算符 for (int i = 0; i < opeatorList.size(); i++) { if (opeatorList.get(i).equals('/')) { numList.set(i, (numList.get(i) * denominator / numList.get(i + 1))); numList.remove(i + 1); opeatorList.remove(i); i--; } } // System.out.println("消除/:" + numList); // System.out.println("消除/:" + opeatorList); } // 消除 * 运算符 if (!divFlag) { if (opeatorList.contains('*')) { for (int i = 0; i < opeatorList.size(); i++) { if (opeatorList.get(i) == '*') { numList.set(i, (numList.get(i) * numList.get(i + 1))); numList.remove(i + 1); opeatorList.remove(i); i--; } } // System.out.println("消除*:" + numList); // System.out.println("消除*:" + opeatorList); } } // 最后进行加减法运算 if (numList.size() != 1) { for (int i = 0; i < opeatorList.size(); i++) { switch (opeatorList.get(i)) { case '+': numList.set(0, (numList.get(0) + numList.get(1))); break; case '-': numList.set(0, (numList.get(0) - numList.get(1))); break; default: break; } numList.remove(1); } } // 計算完成 if (divFlag) { answer = reductionFun(denominator, numList.get(0)); return answer; } else { answer = "" + numList.get(0); return answer; } } /** * 约分化简 * * @param denominator * @param numerator * @return */ public String reductionFun(int denominator, int numerator) { if (denominator == 0) { System.out.println("分母不能为0"); return null; } if (numerator == 0) { return "0"; } // 判断符号 String flag; if ((denominator < 0 && numerator < 0) || (denominator > 0 && numerator > 0)) { flag = ""; } else { flag = "-"; } denominator = Math.abs(denominator); numerator = Math.abs(numerator); // 即求出最大公因数 int smaller = numerator > denominator ? numerator : denominator; int maxCommonFactor = 1; for (int i = 1; i <= smaller; i++) { if (numerator % i == 0 && denominator % i == 0) { maxCommonFactor = i; } } // 可化为整数 if (((numerator / maxCommonFactor) % (denominator / maxCommonFactor)) == 0) { int res = (numerator / maxCommonFactor) / (denominator / maxCommonFactor); if (res == 0) { return "" + res; } return flag + res; } return flag + numerator / maxCommonFactor + "/" + denominator / maxCommonFactor; } /** * 返回重复题目集合信息 * * @return */ public List<String> getRepeatInfo() { List<String> repeatInfo = new ArrayList<String>(); // 判断答案是否一样 for (int i = 0; i < answersList.size() - 1; i++) { for (int j = i + 1; j < answersList.size(); j++) { if (answersList.get(i).equals(answersList.get(j))) { // 判断两个计算数是否含有完全一样的所有数字 if (number.get(i).containsAll(number.get(j))&&number.get(j).containsAll(number.get(i))) { // 判断两个运算符的每一个位置是否一致 boolean flag = true; for (int k = 0; k < opeator.get(i).size(); k++) { if (!opeator.get(i).get(k).equals(opeator.get(j).get(k))) { flag = false; break; } } if (flag) { repeatInfo.add("第" + (i+1) + "道题与第" + (j+1) + "道题重复,题型:" + questionsList.get(i)+"="+questionsList.get(j)); } } } } } return repeatInfo; } public String getQuestion() { return question; } public void setQuestion(String question) { this.question = question; } public String getAnswer() { return answer; } public void setAnswer(String answer) { this.answer = answer; } public List<String> getQuestionsList() { return questionsList; } public void setQuestionsList(List<String> questionsList) { this.questionsList = questionsList; } public List<String> getAnswersList() { return answersList; } public void setAnswersList(List<String> answersList) { this.answersList = answersList; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; } public int getOperatorNum() { return operatorNum; } public void setOperatorNum(int operatorNum) { this.operatorNum = operatorNum; } public List<String> getMathList() { return mathList; } public void setMathList(List<String> mathList) { this.mathList = mathList; } public int getQuestionNum() { return questionNum; } public void setQuestionNum(int questionNum) { this.questionNum = questionNum; } public List<List<Integer>> getNumber() { return number; } public void setNumber(List<List<Integer>> number) { this.number = number; } public List<List<Character>> getOpeator() { return opeator; } public void setOpeator(List<List<Character>> opeator) { this.opeator = opeator; } public String toString() { return "MathFactory [radius=" + radius + ", operatorNum=" + operatorNum + "]"; } }
对于该类的使用,主要成员变量和其方法如下:
private int radius;// 题目中数值的范围,不包括上限
private int operatorNum;// 运算符个数
private int questionNum;// 运算符个数
private String question;// 单个题目
private String answer;// 单个答案
private List<String> questionsList;// 题目集合
private List<String> answersList;// 答案集合
private List<String> mathList;// 题目加答案集合
private List<List<Integer>> number;// 所有计算数的集合
private List<List<Character>> opeator;// 所有运算符的集合
String productSingleMath(); /*生产一道计算题* @return String */
List<String> productMathQuestionList();/* 生产计算题集合 * @return List*/
String createEasyMathQuestion();/ * 生成简单的数学题目,整数运算* @return String*/
List<String> productMathAnswerList() /* 生成答案集合 * @return LIst*/
String createMathAnswer(); /*生成答案* @return String*/
public String reductionFun(int denominator, int numerator);/* 约分化简 * @param denominator* @param numerator* @return String*/
public List<String> getRepeatInfo();/** 返回重复题目集合信息 * @return List*/
通过对该类的使用可以完成需求分析中的所有内容,除判断重复外,在判断重复题目时存在不完善的地方。
下面是类中几个关键步骤的处理
关于判断题目重复
判断题目重复例子:
重复:1、1+2+3+4与1+2+3+4
2、2+1+3+4与1+2+3+4(计算是左结合)
不重复:3、1+2+3 与3+2+1(计算是左结合)
我判断题目是否一样的思路是:
首先,判断答案是否一样;
其次,式子A中的所有的计算数式子B中是否都有,我用的时集合存储计算数,listA.containsAll(listB)与listB.containsAll(listA)都为true时则认为都有;
最后,式子A中的所有运算符和式子B中所有运算符的所有位置与符号是否一致。
当满足三个条件则,则为重复。这解决了上述重复例子2的判断情况,但对于不重复例子3却存在误判的情况,将其判为重复了。(希望大神有更好的方法分享下)
关于对String类型的题目的处理
由于java是强类型的语言,对与类型有这严格的要求,这就使得我们对于一个"1+2+3/4" String的字符串无法直接计算出答案。(我们从文本中得到的题目String类型)
对于String类型的字符串题目处理主要步骤是:
1、通过String的split()方法,通过分割 split("[+ * / -]") ,得到计算数集合;
2、通过String的toCharArray()方法, 将字符串题目转换成字符数组,再遍历数组,筛选得到运算符集合。
关于对题目得四则运算处理
从上面得方法得到计算数和运算符的集合。由于本次作业要求,小数由分数表示。存在除号的时候,不仅要考虑其优先级,还要对其计算结果判断是否为整数,否则用分数表示。
从而我们可以将问题进行判断处理,分成两种情况,一种是运算符中存在 ‘/’ ,另一种则没有。
关于计算问题我们从简单入手。
首先,是最简单的加减法;
由于加减法的优先集是一样的,我们对于一个题目“ 1+2+3-4-5” 进行处理后,得到的计算数和运算符集合分别是 [ 1 2 3 4 5 ] [ + + - - ] ,这里我们用集合来存的原因,
是我们只要根据运算符对计算数进行相应的计算,完成后,将结果赋值给进行运算的首个计算数即可将其移除第二个进行运算的计算数。
例如上述例子:遍历运算符进行运算时, [ 1 2 3 4 5 ] [ + + - - ] 进行完第一次运算结束后,集合变成 [ 3 3 4 5 ] [ + - - ] ,直至最后运算结束集合为 [ 15 ] [ ] ,最后计算数只剩下一个元素即位计算结果, 而运算符集合被清空。
接着,当我们引入乘法时;
由于乘法的优先级大于加减法,我们可以先对运算符集合先进行遍历,选出 ‘ * ’ 的符号,根据位置将其两边的计算数进行乘法运算,完成后将移除掉 ‘ * ’ 和 后一个计算数,跟前面的做法一样。 例如: 题目是 " 1+2*3+4 " [ 1 2 3 4 ] [ + * + ] ,进行处理后 [ 1 6 4 ] [ + + ],如此以来题目就简化成了加减法运算。
最后,是除法;
这道题目由于引入分数,使得难度较大。
当一个题目没有除号时,我们完全使用上面的两个步骤就可以完成。
当存在除号时,我们要考虑几个情况:
1、除号后面不能跟计算数0;这使得我们再产生一定范围内得随机数时,要考虑前一个运算符产生情况。由于我们这里运算符和计算数是先后分开产生的,比较好处理,具体看代码。
2、分数相加相减的情况,例如:1+2/3+4/5,这种情况下,我们如何处理。这里我的做法是,找一个公倍数,将其所有数字进行相应倍化,进行加减法后再除去这个公倍数,则得到答案。1+2/3+4/5 处理后得到计算数集合和运算符集合 [ 1 2 3 4 5] [ + / + / ] ,根据 ‘ / ’ 除号后的计算数,我们找到公倍数 15 ,而后我们将移除 ‘ / ’除号 和除数 。b对其余的计算数进行相应的倍化得到 [15 10 12 ] [ + + ] ,剩下的就是加减法运算,到最后再将结果除以 公倍数 ,则得到答案。这里的答案要进行化简,可以整除则用整数表示,不可整除,也有约分处理,具体看代码public String reductionFun(int denominator, int numerator);/* 约分化简 * @param denominator* @param numerator* @return String*/
3、存在复杂的运算时,例如:1+2/3/4*5/6+7 的情况。这时我的处理是,先对题目进行第一步的化简,将 ‘ / ’ 除号后 连续乘除的情况,化简成只有一次除法。
题目: “1+2/3/4*5/6+7” [ 1 2 3 4 5 6 7 ] [ + / / * / + ]
第一步化简结束后 [ 1 10 72 7 ] [ + / + ] 题目:“1+10/72+7 ”,这就变成了第二种情况
做法是:读取到第一个‘ / ’ 除号后,判断后续运算符号,若是 ‘* ’乘号 则乘 读到的第一个 ‘/ ’除号前的数,乘完后,移除运算符和后面的相应计算数;若是 ‘/ ’除号 则乘第一个读到的 ‘/ ’除号后的数,乘完后,移除运算符和后面的相应计算数;
4、存在优先级分开的题目时,例如 1+2*3+4/5*6/7 。 我通常们要先把2*3和4/5*6进行计算后,再进行加法运算。 然而为了能更加利用前面的代码,我先将题目进行了,第三种情况的处理 ,得到 1 + 2*3 + 24/35 ,在对其进行乘法运算,消除乘号得到 1 + 6 +24/35 。这就有回到了第二种情况。情况往越来越容易的方面发展。
所以,当题目中包含除号时,我们主要流程是:
第一步,将除号后面的连续乘除运算,化简成,只有一个除数。例如:1/2/3 =》 1/6, 1+2*3+4/5/6*7 =》1+2*3+28/30
第二步, 进行*乘法运算,将*乘号去除 1+6+28/30
第三步,给计算数乘公倍数,倍化处理,去除/ 除号 30+180+28
第四步,加减法运算 238
最后,将结果除公倍数,进行约分 238/30 约分后 119/15
合理的代码结构,将节省代码量。
这就是主要的几块关键性代码,之后,合理的使用该类,可以实现这次要设计的程序的大部分功能。
通过实例该类,设置以下
private int radius;// 题目中数值的范围,不包括上限
private int operatorNum;// 运算符个数
private int questionNum;// 运算符个数
三个成员变量参数后,调用以下方法
List<String> productMathQuestionList();/* 生产计算题集合 * @return List*/
List<String> productMathAnswerList() /* 生成答案集合 * @return LIst*/
即可产生对应的题目与答案集合,也可自定义设置题目集合,
private List<String> questionsList;// 题目集合
而后调用
List<String> productMathAnswerList() /* 生成答案集合 * @return LIst*/
方法,生成答案,
调用
public List<String> getRepeatInfo();/** 返回重复题目集合信息 * @return List*/
返回题目重复信息集合。
关于题目与答案验证小功能,忘了写,但只要,先导入题目,生成答案,再与自己给定答案相比对下,可以得出验证结果。
之后的总结,关于自我估计的任务时间表格,一开始还会认真的计划下,写了需求分析文档,部分的设计文档,但再开使写代码后,常常因为一个bug的卡壳,时间有时候就会花上大半天的时间,写完一部分代码后,总会有更好的思路出现,认为,这样的代码更加合理,于是有将代码重构了下。这使得时间和表格中的任务分界线很模糊,不知道怎么填。
以下是那张不知道怎么填的表。
目前情况就是这样,明天再处理下后半部分。