变更后的需求
1.程序运行在终端或控制台上
2.程序可以扩展出优秀界面,即核心部分可重用并可拓展
3.题目可包含括号
4.限定题目数量,"精美"打印输出
5.支持分数出题和运算
6.约分和带分数
分析和设计
1.原出题程序为网页版,现在新建了一个加载出题核心的类用于在终端中提供用户使用,并带有提示和运行逻辑。
2.核心出题类提供多种需求参数列表,并作出相应处理,为不同难度和需求生成相应题目。
3.括号和题目计算部分可以使用"逆波兰","后缀表达式"或"堆栈",本程序计算部分选择使用栈维护运算数和运算符,用ArrayList维护随机生成的括号位置列表,然后使用TreeMap计算并保存应该生成括号的位置,最后在题目生成的时候进行拼接。
4.题目数量可以由用户自行输入,输入题目难度和数量以后可选择答题模式或者打印模式,答题模式可以边答题边判题,错误的给出正确答案,最后生成答题结果,即答题正确数量和错误数量。打印模式则直接生成相应数量题目并给出结果。
5.分数运算则新建Fraction类,并添加加减乘除的相应函数,注意java不支持运算符重载,Fraction类包含随机生成函数,可以生成随机的分数,并保证不为0或者分母为零,以免发生除零错误。
6.类中还包含一个约分函数,方便运算和减少溢出情况的发生。带分数可以使用%运算符来求出,通过重新toSting函数来输出带分数。
程序的使用设计
程序启动时用户选择题目难度或者查看题目难度说明,然后选择出题数量,接着选择运行模式,即直接查看答案的打印模式还是边出题边答题的答题模式。
简易运行流程图:
部分代码实现
Fraction类定义(分数类),截取部分
1 package cn.edu.nenu.cw2016.zjs.paperUtil; 2 3 public class Fraction { 4 public int up; 5 public int down; 6 7 public Fraction(int up, int down) { 8 if (down == 0 | up == 0) { 9 System.out.println("divided by zero error"); 10 return; 11 } 12 int smaller = up > down ? up : down; 13 int maxCommonFactor = 1; 14 for (int i = 1; i <= smaller; i++) { 15 if (up % i == 0 && down % i == 0) { 16 maxCommonFactor = i; 17 } 18 } 19 20 this.up = up / maxCommonFactor; 21 this.down = down / maxCommonFactor; 22 } 23 24 public Fraction(Fraction f) { 25 if (f.down == 0 | up == 0) { 26 System.out.println("divided by zero error"); 27 return; 28 } 29 30 f = f.gcd(f); 31 this.up = f.up; 32 this.down = f.down; 33 } 34 35 public Fraction gcd(Fraction f) { 36 int smaller = f.up > f.down ? f.up : f.down; 37 int maxCommonFactor = 1; 38 for (int i = 1; i <= smaller; i++) { 39 if (f.up % i == 0 && f.down % i == 0) { 40 maxCommonFactor = i; 41 } 42 } 43 f.up = f.up / maxCommonFactor; 44 f.down = f.down / maxCommonFactor; 45 46 return f; 47 } 48 49 public String toString() { 50 if (down == 1) 51 return "" + up; 52 if(Math.abs(up)/down>0){ 53 return up>0?up/down+" "+up%down+"/"+down:"-"+Math.abs(up)/down+" "+Math.abs(up)%down+"/"+down; 54 } 55 return up + "/" + down; 56 } 57 58 public Fraction add(Fraction f) { 59 Fraction a = new Fraction(up, down); 60 a.up = f.up * a.down + a.up * f.down; 61 a.down = a.down * f.down; 62 63 return a.gcd(a); 64 } 65 66 public Fraction minus(Fraction f) { 67 Fraction a = new Fraction(up, down); 68 a.up = a.up * f.down - f.up * a.down; 69 a.down = a.down * f.down; 70 71 return a.gcd(a); 72 } 73 74 public Fraction multiply(Fraction f) { 75 Fraction a = new Fraction(up, down); 76 a.up = a.up * f.up; 77 a.down = a.down * f.down; 78 return a.gcd(a); 79 } 80 81 public Fraction divide(Fraction f) { 82 Fraction a = new Fraction(up, down); 83 a.up = a.up * f.down; 84 a.down = a.down * f.up; 85 return a.gcd(a); 86 } 87 88 public Fraction changeSign(){ 89 up = -up; 90 return this; 91 } 92 93 public static Fraction getRandiom(int Max) { 94 return new Fraction((int) (Math.random() * Max / 2) + 1, (int) (Math.random() * Max / 2) + 1); 95 } 96 97 public static Fraction getRandiom(int Max, boolean isInt) { 98 return new Fraction((int) (Math.random() * Max / 2) + 1, isInt ? 1 : (int) (Math.random() * Max / 2) + 1); 99 } 100 }
题目生成
1 public String generateQuestion(int numOfOperand, int rangeMin, int rangMax, SupportedOperation[] operation, 2 boolean isFractional, boolean hasbracket) { 3 String question = ""; 4 int[] ioperands = null; 5 ArrayList<Fraction> af = new ArrayList<Fraction>(); 6 SupportedOperation[] so = null; 7 if (numOfOperand < 2) { 8 System.out.println("操作数数量至少为2"); 9 return ""; 10 } 11 if (rangMax > 500) { 12 System.out.println("操作数数最大值不能超过500"); 13 return ""; 14 } 15 getBcPrint(numOfOperand); 16 if (!isFractional) { 17 ScriptEngine se = new ScriptEngineManager().getEngineByName("JavaScript"); 18 ioperands = new int[numOfOperand]; 19 for (int i = 0; i < numOfOperand; i++) { 20 ioperands[i] = (int) (Math.random() * rangMax / 2 + 1); 21 22 } 23 so = new SupportedOperation[numOfOperand - 1]; 24 for (int i = 0; i < operation.length; i++) { 25 if (operation[i] == SupportedOperation.ALL) { 26 operation = new SupportedOperation[4]; 27 operation[0] = SupportedOperation.ADD; 28 operation[1] = SupportedOperation.MINUS; 29 operation[2] = SupportedOperation.MULTIPLY; 30 operation[3] = SupportedOperation.DIVIDE; 31 32 } 33 } 34 // 除法運算,保证整除 35 int value = 0; 36 for (int j = numOfOperand - 1; j > 0; j--) { 37 so[numOfOperand - 1 - j] = operation[(int) (Math.random() * operation.length)]; 38 } 39 for (int j = numOfOperand - 2; j >= 0; j--) { 40 if (so[j] == SupportedOperation.DIVIDE) { 41 if (value < 1) { 42 ioperands[j] = ioperands[j] * ioperands[j + 1]; 43 value++; 44 45 } else { 46 so[j] = operation[(int) (Math.random() * (operation.length - 2))]; 47 } 48 } 49 } 50 // 输出括号 51 for (int i = 0; i < numOfOperand - 1; i++) { 52 if (frequency.containsKey(i)) { 53 if (direction.get(i) == 0) { 54 for (int k = 0; k < frequency.get(i); k++) { 55 question += "("; 56 } 57 } 58 } 59 question += ioperands[i]; 60 if (frequency.containsKey(i)) { 61 if (direction.get(i) == 1) { 62 for (int k = 0; k < frequency.get(i); k++) { 63 question += ")"; 64 } 65 } 66 } 67 question += so[i]; 68 } 69 if (frequency.containsKey(numOfOperand - 1)) { 70 if (direction.get(numOfOperand - 1) == 0) { 71 for (int k = 0; k < frequency.get(numOfOperand - 1); k++) { 72 question += "("; 73 } 74 } 75 } 76 question += ioperands[numOfOperand - 1]; 77 if (frequency.containsKey(numOfOperand - 1)) { 78 if (direction.get(numOfOperand - 1) == 1) { 79 for (int k = 0; k < frequency.get(numOfOperand - 1); k++) { 80 question += ")"; 81 } 82 } 83 } 84 85 try { 86 Integer d = (Integer) se.eval(question); 87 answer = "" + d; 88 } catch (Exception e) { 89 generateQuestion(numOfOperand, rangeMin, rangMax, operation, isFractional, hasbracket); 90 } 91 92 } else { 93 for (int i = 0; i < numOfOperand; i++) { 94 af.add(Fraction.getRandiom(rangMax)); 95 } 96 97 so = new SupportedOperation[numOfOperand - 1]; 98 for (int i = 0; i < operation.length; i++) { 99 if (operation[i] == SupportedOperation.ALL) { 100 operation = new SupportedOperation[4]; 101 operation[0] = SupportedOperation.ADD; 102 operation[1] = SupportedOperation.MINUS; 103 operation[2] = SupportedOperation.MULTIPLY; 104 operation[3] = SupportedOperation.DIVIDE; 105 106 } 107 } 108 question += af.get(0); 109 for (int j = 0; j < numOfOperand - 1; j++) { 110 so[j] = operation[(int) (Math.random() * operation.length)]; 111 question += (so[j] == SupportedOperation.DIVIDE ? "÷" : so[j].toString()) + af.get(j + 1); 112 113 } 114 answer = getanswer(af, so).toString(); 115 try { 116 } catch (Exception e) { 117 e.printStackTrace(); 118 } 119 } 120 121 return question; 122 123 } 124 }
括号维护和答案计算
1 public String answer; 2 3 Stack<SupportedOperation> oplist = new Stack<SupportedOperation>(); 4 Stack<Fraction> numlist = new Stack<Fraction>(); 5 ArrayList<Integer[]> bclist; 6 TreeMap<Integer, Integer> frequency; 7 TreeMap<Integer, Integer> direction; 8 9 private void getBcPrint(int numOfOperand) { 10 bclist = new ArrayList<Integer[]>(); 11 if (numOfOperand > 2) { 12 int bcnum = (int) (Math.random() * (numOfOperand - 2)); 13 for (int n = 0; n < bcnum; n++) { 14 Integer[] bracket = new Integer[2]; 15 bracket[0] = (int) (Math.random() * (numOfOperand - 2)); 16 bracket[1] = (int) (Math.random() * (numOfOperand - 2 - bracket[0]) + bracket[0]); 17 if (bracket[0] == bracket[1]) { 18 bracket[1]++; 19 } 20 boolean canput = true; 21 for (int i = 0; i < bclist.size(); i++) { 22 Integer[] tmp = bclist.get(i); 23 if (bracket[0] < tmp[0] & bracket[1] >= tmp[0] & bracket[1] < tmp[1]) { 24 canput = false; 25 break; 26 } else if (bracket[1] > tmp[1] & bracket[0] > tmp[0] & bracket[0] <= tmp[1]) { 27 canput = false; 28 break; 29 } else if (bracket[0] == tmp[0] & bracket[1] == tmp[1]) { 30 31 } 32 } 33 if (canput) { 34 bclist.add(bracket); 35 } 36 } 37 38 } 39 frequency = new TreeMap<Integer, Integer>(); 40 direction = new TreeMap<Integer, Integer>(); 41 for (int i = 0; i < bclist.size(); i++) { 42 Integer[] tmp = bclist.get(i); 43 if (frequency.containsKey(tmp[0])) { 44 frequency.put(tmp[0], frequency.get(tmp[0]) + 1); 45 } else { 46 frequency.put(tmp[0], 1); 47 direction.put(tmp[0], 0); 48 } 49 if (frequency.containsKey(tmp[1])) { 50 frequency.put(tmp[1], frequency.get(tmp[1]) + 1); 51 } else { 52 frequency.put(tmp[1], 1); 53 direction.put(tmp[1], 1); 54 } 55 } 56 } 57 58 public Fraction getanswer(ArrayList<Fraction> frlist, SupportedOperation[] so) { 59 60 numlist.push(frlist.get(0)); 61 for (int n = 0; n < so.length; n++) { 62 switch (so[n]) { 63 case ADD: 64 oplist.push(so[n]); 65 numlist.push(frlist.get(n + 1)); 66 break; 67 case MINUS: 68 oplist.push(SupportedOperation.ADD); 69 numlist.push(frlist.get(n + 1).changeSign()); 70 break; 71 case MULTIPLY: { 72 Fraction r = numlist.pop().multiply(frlist.get(n + 1)); 73 numlist.push(r); 74 } 75 break; 76 case DIVIDE: { 77 Fraction r = numlist.pop().divide(frlist.get(n + 1)); 78 numlist.push(r); 79 } 80 break; 81 default: 82 System.out.println("不支持的运算"); 83 break; 84 } 85 } 86 87 while (!oplist.isEmpty()) { 88 Fraction answer = numlist.pop(); 89 switch (oplist.pop()) { 90 case ADD: { 91 answer = answer.add(numlist.pop()); 92 numlist.push(answer); 93 } 94 break; 95 case MINUS: { 96 answer = answer.minus(numlist.pop()); 97 numlist.push(answer); 98 } 99 break; 100 default: 101 System.out.println("不支持的运算"); 102 break; 103 } 104 105 } 106 107 return numlist.pop(); 108 }
程序运行结果:
简单难度答题:
普通难度出题打印:
复杂题目出题打印:
程序退出:
结对编程体会
结对编程可以了解队友的编程习惯和解决问题的思路,学习对方的解决问题方式,而且当一个人遇到瓶颈的时候,另一个人可能会提前想出思路,多种解决方式的时候可以择优选择。这种工作模式有点像cpu的双核处理器,两个线程同时工作,一定程度上可以快速的开发项目,并减少代码合并带来的麻烦。这个工作模式以前也是尝试过的,尤其是主要作为指导在旁边跟进时效率不一定会很高,思维的协调不一定比直接看结果来的快,不过对于学习阶段很是受用。
争论点:
1.整除问题:输出结果在尽量调节为整数的情况下可能因括号的出现导致结果为小数或者分数,对于这样的情况选择直接重出题目(仅限简单和普通难度的问题)
2.分数减法入栈出栈顺序:计算结果采用栈来解决,出栈时仅剩加减法需要运算,当出现减法,甚至连减时选择将减法换为加法运算,然后改变操作数符号,即Fraction.changSign();
3.括号列表维护:括号生成是根据操作数数量决定的,生成括号位置列表时排除无效和冗余括号,最后将括号列表转化位置和括号方向、数量的Map,用于打印括号,即先有操作数后有括号的设计,而不是括号匹配的方式
4.连除问题:用计数器检测除法的出现,防止连除,但是不会妨碍一个题目出现两个或更多的除法运算(仅限简单和普通难度)。因为在整数运算中,为保证可以整除,需要改变被除数,这样做太多次连除会导致最顶层操作数过大,不利于题目平衡性
花费时间较长的问题:括号列表维护
结对编程照片: