现代软件工程 第一章 概论 第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);
    }

}

 

posted @ 2016-09-11 16:36  vrfighter  阅读(333)  评论(0编辑  收藏  举报