• 现代软件工程 第一章 概论 第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);
        }
    
    }
  • 相关阅读:
    《一起》个人进展——Day05
    《一起》个人进展——Day04
    《一起》个人进展——Day03
    《一起》个人进展——Day02
    《一起》个人进展——Day01
    this
    java_流
    input _文本框回车或者失去光标触发事件
    removeAll
    SysLog简介和java操作实例
  • 原文地址:https://www.cnblogs.com/vrfighter/p/5862077.html
Copyright © 2020-2023  润新知