现代软件工程 第一章 概论 第1题——邓琨
题目要求:
第一步: 像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”, 分别满足下面的各种需求。下面这些需求都可以用命令行参数的形式来指定:
a) 除了整数以外,还要支持真分数的四则运算。 (例如: 1/6 + 1/8 = 7/24)
b) 让程序能接受用户输入答案,并判定对错。 最后给出总共 对/错 的数量。
c) 逐步扩展功能和可以支持的表达式类型,最后希望能支持下面类型的题目 (最多 10 个运算符,括号的数量不限制):
25 - 3 * 4 - 2 / 2 + 89 = ?
1/2 + 1/3 - 1/4 = ?
(5 - 4 ) * (3 +28) =?
d) 一次可以批量出 100 道以上的题目,保存在文本文件中, 并且保证题目不能重复,(1+2) 和 (2+1) 是重复的题目。 怎么保证题目不重复呢,请看详细题目要求
和同学们比较一下各自程序的功能、性能、实现方法的异同等等。
如何对一个表达式求值? 参考资料:
http://hczhcz.github.io/2014/02/27/shunting-yard-algorithm.html
https://en.wikipedia.org/wiki/Shunting-yard_algorithm
鉴于该问题的难度与工作量,我将该问题拆分成3个小阶段进行实现:
1:复合四则运算的求值
2:随机生成复合四则运算
3:增加对真分数和假分数的支持
分析:1.对于复合四则运算的求值,可以根据堆栈的特性,利用算数符合的优先级比较,对复合四则运算求值,详细的算法过程将在下文进行介绍。
2.随机生成复合四则运算是本题目的一个难点,在进行思考过后,我觉得可以采用多次随机过程,生成四则运算表达式。第一次,随机参与计算的数字个数,第二次,随机数字的数值,第三次,随机数字间的四则运算符,第四次,随机括号的添加。注意,由于可能存在除0运算,所以要对这种错误情况进行排除。由于第三次和第四次随机过程的特殊性,即使在第三步出现例如:3 / 0 + 1 的运算表达式,但是如果第四次随机过程在算式 0 + 1 外生成括号,造成 3 / ( 0 + 1 )的结果,这种算式也是正确的。因此,不能保证在随机生成复合四则运算的过程中进行除0的判断。
对于这个问题,我的个人思路是:在生成每一个复合四则运算后,进行复合运算求值,并在算法每一次求值过程中就行判断,如果出现除0错误,就抛出异常,标记当前生成的四则运算式子非法,进行重新生成。虽然除0的情况很多,这样会浪费大量的时间进行判断,但是由于问题的时间复杂度和空间复杂度都非常小,可以忽略不计。
3.在增加真分数和假分数运算时候,只需要对每一种四则运算进行一次条件判断,因此无较大的难度。
一、复合四则运算的求值
表达式求值的算法:
我采用的是“算符优先法”,在表达式中,优先级的顺序是:
1、括号的优先级最高,对括号内的各种运算符有:先乘除,后加减,同级运算从做至右
2、先括号内,后括号外,多层括号,由内向外。
操作数可以是常量、变量、常数。
运算符有算术运算符、关系运算符、逻辑运算符。
界符包括左右括号算式结束符。
运算符和界符统称为“算符”。
在算符集中,在每一个运算步,相邻的算符c1 和c2之间的关系是如下三种情况(c1出现在c2之前):
c1<c2,c1的优先级低于c2
c1=c2,c1的优先级等于c2
c1>c2,c1的优先级大于c2
下图描述的算符的优先级:
为实现算符优先算法,在这里用了两个工作栈。一个存放算符OPTR,另一个存放数据OPND。算法思想是:
(1)首先置数据栈为空栈,表达式起始符“#”为算符栈的栈底元素
(2)自左向右扫描表达式,读到操作数进OPND栈,读到运算符,则和OPTR栈顶元素比较(栈顶元素为c1,读到的算符为c2);
若c1<c2,则c2进栈继续扫描后面表达式;
若c1=c2,则(“=”),即括号内运算结束,将c1出栈,并且c2放弃,继并在操作数栈扫描后面表达式;
若c1>c2,则将c1出栈,并在操作数栈取出两个元素a和b按c1做运算,运算结果进OPND.
重复直到表达式求值完毕。
例如:表达式3*(7-2),求值过程如下表:
步骤 | OPTR栈 | OPND栈 | 输入字符 | 主要操作 |
1 | # | 3*(7-2)# | PUSH(OPND,'3') | |
2 | # | 3 | *(7-2)# | PUSH(OPTR,'*') |
3 | #* | 3 | (7-2)# | PUSH(OPTR,'(') |
4 | #*( | 3 | 7-2)# | PUSH(OPND,'7') |
5 | #*( | 3 7 | -2)# | PUSH(OPTR,'-') |
6 | #*(- | 3 7 | 2)# | PUSH(OPND,'2') |
7 | #*( | 3 7 2 | )# | operate(7-2) |
8 | #*( | 3 5 | )# | pop() |
9 | #* | 15 | # | operate(3*5) |
10 | # | 15 | # | PUSH(OPTR,'#') |
为使两个算符比较方便,给算符设置优先级,如下表,其中c1为栈内元素,c2为栈外元素:
算符比较算法:
char Precede(char c1,char c2) {int c_temp1,c_temp2; switch(c1) { case ‘*’: case ‘/’:c_temp1=4;break; case ‘+’: case ‘-’:c_temp1=2;break; case ‘(’:c_temp1=0;break; case ‘)’:c_temp1=5;break; case ‘#’:c_temp1=-1; }
switch(c2) { case ‘*’: case ‘/’:c_temp2=3;break; case ‘+’: case ‘-’:c_temp2=1;break; case ‘(’:c_temp2=5;break; case ‘)’:c_temp2=0;break; case ‘#’:c_temp2=-1;} if(c_temp1<c_temp2) return(‘<‘); if(c_temp1=c_temp2) return(‘=‘); if(c_temp1>c_temp2) return(‘>‘); }
int express() { Initstack(OPTR);Push(OPTR,’#’); InitStack(OPND); w=getchar(); while(w!=‘#’||GetTop(OPTR)!=‘#’) {if(!In(w,OP)){Push(OPND,w);w=getchar();} else //OP是操作符集合 switch(Precede(GetTop(OPTR),w)) {case ‘<‘: Push(OPTR,w);w=getchar();break; case ‘=‘: Pop(OPTR);w=getchar();break; case ‘>‘: op=Pop(OPTR);b=Pop(OPND);a=Pop(OPND); push(OPND,Operate(a,op,b));break;}} return(Getop(OPND) );
算符优先算法求表达式值:
OperandType EvaluateExpression() { InitStack (OPTR); Push ( OPTR,’#’); InitStack (OPND); c=getchar(); while (c!=‘#’|| GetTop(OPTR)!=‘#’) { if(!In(c,OP)){Push(OPND,c); c=getchar();} else switch(Precede(GetTop(OPTR),c){ case ‘<‘://栈顶元素优先权低 Push(OPTR,c); c=getchar(); break; case ‘=‘://脱括号 Pop(OPTR,x); c=getchar(); break; case ‘>’://退栈并计算 Pop(OPTR,theta); Pop(OPND,b); Pop(OPND,a); Push(OPND, Operate(a,theta,b)); break; }//switch }//while return GetTop(OPND); }//EvaluateExpression
public class EvaluateExpression { // 运算符优先级关系表 private Map<String, Map<String, String>> priorityMap = new HashMap<String, Map<String, String>>(); private LinkedStack<String> optStack = new LinkedStack<String>(); // 运算符栈 private LinkedStack<Double> numStack = new LinkedStack<Double>(); // 操作数栈 /** * 计算表达式 * @param exp 四则运算表达式, 每个符号必须以空格分隔 * @return */ public double calcualte(String exp) { StringTokenizer st = new StringTokenizer(exp); while (st.hasMoreTokens()) { String token = st.nextToken(); process(token); } return numStack.pop(); } /** * 读入一个符号串。 * 如果是数字,则压入numStack * 如果是运算符,则将optStack栈顶元素与该运算符进行优先级比较 * 如果栈顶元素优先级低,则将运算符压入optStack栈,如果相同,则弹出左括号,如果高,则取出2个数字,取出一个运算符执行计算,然后将结果压入numStack栈中 * @param token */ private void process(String token) { while (false == "#".equals(optStack.getTop()) || false == token.equals("#")) { // token is numeric if (true == isNumber(token)) { numStack.push(Double.parseDouble(token)); break; // token is operator } else { String priority = priority(optStack.getTop(), token); if ("<".equals(priority)) { optStack.push(token); break; } else if ("=".equals(priority)) { optStack.pop(); break; } else { double res = calculate(optStack.pop(), numStack.pop(), numStack.pop()); numStack.push(res); } } } } /** * 执行四则运算 * @param opt * @param n1 * @param n2 * @return */ private double calculate(String opt, double n1, double n2) { if ("+".equals(opt)) { return n2 + n1; } else if ("-".equals(opt)) { return n2 - n1; } else if ("*".equals(opt)) { return n2 * n1; } else if ("/".equals(opt)) { return n2 / n1; } else { throw new RuntimeException("unsupported operator:" + opt); } } /** * 检查一个String是否为数字 * @param token * @return */ private boolean isNumber(String token) { int LEN = token.length(); for (int ix = 0 ; ix < LEN ; ++ix) { char ch = token.charAt(ix); // 跳过小数点 if (ch == '.') { continue; } if (false == isNumber(ch)) { return false; } } return true; } /** * 检查一个字符是否为数字 * @param ch * @return */ private boolean isNumber(char ch) { if (ch >= '0' && ch <= '9') { return true; } return false; } /** * 查表得到op1和op2的优先级 * @param op1 运算符1 * @param op2 运算符2 * @return ">", "<" 或 "=" */ public String priority(String op1, String op2) { return priorityMap.get(op1).get(op2); } /** * 构造方法,初始化优先级表 */ public EvaluateExpression() { // initialize stack optStack.push("#"); // initialize priority table // + Map<String, String> subMap = new HashMap<String, String>(); subMap.put("+", ">"); subMap.put("-", ">"); subMap.put("*", "<"); subMap.put("/", "<"); subMap.put("(", "<"); subMap.put(")", ">"); subMap.put("#", ">"); priorityMap.put("+", subMap); // - subMap = new HashMap<String, String>(); subMap.put("+", ">"); subMap.put("-", ">"); subMap.put("*", "<"); subMap.put("/", "<"); subMap.put("(", "<"); subMap.put(")", ">"); subMap.put("#", ">"); priorityMap.put("-", subMap); // * subMap = new HashMap<String, String>(); subMap.put("+", ">"); subMap.put("-", ">"); subMap.put("*", ">"); subMap.put("/", ">"); subMap.put("(", "<"); subMap.put(")", ">"); subMap.put("#", ">"); priorityMap.put("*", subMap); // / subMap = new HashMap<String, String>(); subMap.put("+", ">"); subMap.put("-", ">"); subMap.put("*", ">"); subMap.put("/", ">"); subMap.put("(", "<"); subMap.put(")", ">"); subMap.put("#", ">"); priorityMap.put("/", subMap); // ( subMap = new HashMap<String, String>(); subMap.put("+", "<"); subMap.put("-", "<"); subMap.put("*", "<"); subMap.put("/", "<"); subMap.put("(", "<"); subMap.put(")", "="); //subMap.put("#", ">"); priorityMap.put("(", subMap); // ) subMap = new HashMap<String, String>(); subMap.put("+", ">"); subMap.put("-", ">"); subMap.put("*", ">"); subMap.put("/", ">"); //subMap.put("(", "<"); subMap.put(")", ">"); subMap.put("#", ">"); priorityMap.put(")", subMap); // # subMap = new HashMap<String, String>(); subMap.put("+", "<"); subMap.put("-", "<"); subMap.put("*", "<"); subMap.put("/", "<"); subMap.put("(", "<"); //subMap.put(")", ">"); subMap.put("#", "="); priorityMap.put("#", subMap); } }
具体程序实现如下:
import com.sun.org.apache.bcel.internal.generic.CASTORE; import com.sun.org.apache.bcel.internal.generic.PUSH; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.StringTokenizer; /** * Created by inuyasha on 2016/9/11. */ public class Calculate { // 运算符优先级关系表 private Map<String, Integer> in = new HashMap<String,Integer>(); private Map<String, Integer> out = new HashMap<String ,Integer>(); // 运算符栈 private Stack<String> optStack = new Stack<String>(); // 操作数栈 private Stack<String> numStack = new Stack<String>(); /** * 为运算符赋值优先级别 */ public Calculate(){ in.put("*",4); in.put("/",4); in.put("+",2); in.put("-",2); in.put("(",0); in.put(")",5); in.put("#",-1); out.put("*",3); out.put("/",3); out.put("+",1); out.put("-",1); out.put("(",5); out.put(")",0); out.put("#",-1); } /** * 判断是否为数字 * @param ch * @return */ public boolean isOperater(String ch) { if (ch.charAt(0) >= '0' & ch.charAt(0) <= '9') { return false; } return true; } /** * 返回数字 * @param number * @return */ public int getNumber(String number){ if(number.indexOf('/') != -1){ return 1; }else return Integer.parseInt(number); } public boolean isFraction(String number){ return number.indexOf('/') == 0; } /** * 返回操作符 * @param operater * @return */ public char getOperater(String operater){ return operater.charAt(0); } public String getResult(String input){ input += " #"; String [] strings = input.split(" "); optStack.push("#"); int i = 0; String s = strings[i++]; while (s.charAt(0) != '#' || optStack.peek() != "#"){ if(!isOperater(s)){ numStack.push(s); s = strings[i++]; }else{ if(in.get(optStack.peek()) < out.get(s)){ optStack.push(s); s = strings[i++]; }else if(in.get(optStack.peek()) == out.get(s)){ optStack.pop(); s = strings[i++]; }else{ char op = optStack.pop().charAt(0); String b = numStack.pop(); String a = numStack.pop(); String result = doSimpleCalculate(a,op,b); numStack.push(result); } } } return numStack.pop(); } public String doSimpleCalculate(String a,char op,String b){ if(!isFraction(a) && !isFraction(b)){ switch (op){ case '+': return (Integer.parseInt(a) + Integer.parseInt(b)) + ""; case '-': return (Integer.parseInt(a) - Integer.parseInt(b)) + ""; case '*': return (Integer.parseInt(a) * Integer.parseInt(b)) + ""; case '/': return (Integer.parseInt(a) / Integer.parseInt(b)) + ""; } }else{ return "1"; } return null; } }
import java.util.Random; import java.util.Scanner; /** * Created by inuyasha on 2016/9/10. */ public class Test { public boolean flag = false; public static void main(String []args){ Calculate calculate = new Calculate(); String result = calculate.getResult("( 1 + 2 ) * 4 / ( 4 - 1 )"); System.out.println(result); } }