java字串應用之運算式解析器

來源:互聯網
上載者:User

 一、運算式的組成
    1、數字
    2、運算子:+ - / * ^ % =
    3、圓括弧
    4、變數
二、運算子優先順序
    由高到低分別為:+-(加號或減號)、^、*/%、+-、=
    優先順序相等的運算子按照從左至右的順序計算
三、關鍵技術點
    1、確定運算的優先順序,從高到低分別為:原子項目運算式,包括數字和變數;括號運算式;一元運算式,取數的負數;指數運算式;乘、除、模數運算式;加、減運算式;賦值運算式。
    2、對於每一層級的運算,都由一個方法實現,在方法中先完成比自己高一層級的運算,再處理本層級的運算。因此,在計算整個運算式的主方法中,只需要調用最低層級的運算的實現方法即可。
    3、確定運算式中的分隔字元,(+、-、*、/、%、^、=、(、)、)。利用這些分隔字元將運算式分成多段,每一段叫做一個token,分隔字元也算token。
    4、用長度為26的int數組vars儲存變數的值。
    5、Character的isWhitespace方法判斷字元是否為空白符,用於去掉運算式中的空白符。
    6、Character的isLetter方法判斷字元是否為字母,用於提取運算式中的變數
    7、Character的isDigit方法判斷字元是否為數字,用於擷取運算式中的數字
   
四、示範執行個體

 

 

/** *//**
 * 檔案名稱ExpressionParser.java
 */
package book.oo.String;

/** *//**
 * 運算式解析器
 * @author joe
 *
 */
public class ExpressionParser ...{
    //4種標記類型
    public static final int NONE_TOKEN = 0;    //標記為空白或者結束符
    public static final int DELIMITER_TOKEN = 1;    //標記為分隔字元
    public static final int VARIABLE_TOKEN = 2;    //標記為變數
    public static final int NUMBER_TOKEN = 3;    //標記為數字
   
    //4種錯誤類型
    public static final int SYNTAX_ERROR = 0;    //語法錯誤
    public static final int UNBALPARENS_ERROR = 1;    //括弧沒有結束錯誤
    public static final int NOEXP_ERROR = 2;    //運算式為空白錯誤
    public static final int DIVBYZERO_ERROR = 3;    //被0除錯誤
   
    //針對4種錯誤類型定義的4個錯誤提示
    public static final String[] ERROR_MESSAGES = ...{"Syntax Error", "Unbalanced " +
            "Parentheses", "No Expression Present", "Division by Zero"};
   
    //運算式的結束標記
    public static final String EOE = ""/0";
 
 private String exp; //運算式字串
 private int expIndex; //解析器當前指標在運算式中的位置
 private String token; //解析器當前處理的標記
 private int tokenType; //解析器當前處理的標記類型
 private double[] vars = new double[26]; //變數數組
 /**
  *
  */
 public ExpressionParser() {
 }
 
 /**
  * 解析一個運算式,返回運算式的值
  */
 public double evaluate(String expStr) throws Exception {
  double result;
  this.exp = expStr;
  this.expIndex = 0;
 
  //擷取第一個標記
  this.getToken();
  if (this.token.equals(EOE)) {
   //沒有運算式異常
   this.handleError(NOEXP_ERROR);
  }
 
  result = this.parseAssign(); //處理指派陳述式
  //處理完指派陳述式,應該就是運算式結束符,如果不是,則返回異常
  if(!this.token.equals(EOE)) {
   this.handleError(SYNTAX_ERROR);
  }
  return result;
 }
 
 /**
  * 處理指派陳述式
  */
 public double parseAssign() throws Exception {
  double result; //結果
  int varIndex; //變數下標
  String oldToken; //舊標記
  int oldTokenType; //舊標記的類型
 
  //如果標記類型是變數
  if (this.tokenType == VARIABLE_TOKEN) {
   //儲存當前標記
   oldToken = new String(this.token);
   oldTokenType = this.tokenType;
   //取得變數的索引,本解析器只支援一個字母的變數
   //如果使用者的變數字母長度大於1,則取第一個字母當作變數
   varIndex = Character.toUpperCase(this.token.charAt(0)) - ''A'';
  
   //獲得下一個標記
   this.getToken();
   //如果當前標記不是等號=
   if(!this.token.equals("=")) {
    this.putBack(); //復原
    //不是一個指派陳述式,將標記恢複到上一個標記
    this.token = new String(oldToken);
    this.tokenType = oldTokenType;
   } else {
    //如果當前標記是等號=,即給變數賦值,形式如:a = 3 + 5;
    //則計算等號後面運算式的值,然後再將得到的值賦給變數
    this.getToken();
    //因為加減法的優先順序最低,所以計算加減法運算式
    result = this.parseAddOrSub();
    //將運算式的值賦給變數,並存在執行個體變數vars中
    this.vars[varIndex] = result;
    return result;
   }
  }
  //如果當前標記類型不是變數,或者不是指派陳述式,則用加減法計算運算式的值
  return this.parseAddOrSub();
 }
 
 /** 計算加減法運算式 */
 private double parseAddOrSub() throws Exception {
  char op; //運算子
  double result; //結果
  double partialResult; //子運算式的結果
 
  result = this.pareseMulOrDiv(); //用乘除法計算當前運算式的值
  //如果當前標記的第一個字母是加減號,則繼續進行加減運算
  while ((op = this.token.charAt(0)) == ''+'' || op == ''-'') {
   this.getToken(); //取下一個標記
   //用乘除法計算當前子運算式的值
   partialResult = this.pareseMulOrDiv();
   switch(op) {
   case ''-'':
    //如果是減法,則用已處理的子運算式的值減去當前子運算式的值
    result = result - partialResult;
    break;
   case ''+'':
    //如果是加法,用已處理的子運算式的值加上當前子運算式的值
    result = result + partialResult;
    break;
   }
  }
  return result;
 }
 /**
  * 計算乘除法運算式,包括模數運算
  */
 private double pareseMulOrDiv() throws Exception {
  char op; //運算子
  double result; //結果
  double partialResult; //子運算式結果
  //用指數運算計算當前子運算式的值
  result = this.parseExponent();
  //如果當前標記的第一個字母是乘、除或者模數運算,則繼續進行乘除法運算
  while ((op = this.token.charAt(0)) == ''*'' || op == ''/'' || op == ''%'') {
   this.getToken(); //取下一標記
   //用指數運算計算當前子運算式的值
   partialResult = this.parseExponent();
   switch (op) {
   case ''*'':
    //如果是乘法,則用已處理子運算式的值乘以當前子運算式的值
    result = result * partialResult;
    break;
   case ''/'':
    //如果是除法,判斷當前字運算式的值是否為0,如果為0,則拋出被0除異常
    if(partialResult == 0.0) {
     this.handleError(DIVBYZERO_ERROR);
    }
    //除數不為0,則進行除法運算
    result = result / partialResult;
    break;
   case ''%'':
    //如果是模數運算,也要判斷當前子運算式的值是否為0
    if(partialResult == 0.0) {
     this.handleError(DIVBYZERO_ERROR);
    }
    result = result % partialResult;
    break;
   }
  }
  return result;
 }
 
 /**
  * 計算指數運算式
  */
 private double parseExponent() throws Exception {
  double result; //結果
  double partialResult; //子運算式的值
  double ex; //指數的底數
  int t; //指數的冪
 
  //用一元運算計算當前子運算式的值(底數)
  result = this.parseUnaryOperator();
  //如果當前標記為“^”,則為指數運算
  if (this.token.equals("^")) {
   //擷取下一標記,即獲得指數的冪
   this.getToken();
   partialResult = this.parseExponent();
   ex = result;
   if(partialResult == 0.0) {
    //如果指數的冪為0,則指數的值為1
    result = 1.0;
   } else {
    //否則,指數的值為個數為指數冪的底數相乘的結果
    for (t = (int) partialResult - 1; t > 0; t--) {
     result =result * ex;
    }
   }
  }
  return result;
 }
 
 /**
  * 計算一元運算,+,-,表示正數和負數
  */
 private double parseUnaryOperator() throws Exception{
  double result; //結果
  String op; //運算子
  op = "";
  //如果當前標記類型為分隔字元,而且分隔字元的值等於+或者-
  if((this.tokenType == DELIMITER_TOKEN) && this.token.equals("+") || this.token.equals("-")) {
   op = this.token;
   this.getToken();
  }
  //用括弧運算計算當前子運算式的值
  result = this.parseBracket();
  if(op.equals("-")) {
   //如果運算子為-,則表示負數,將子運算式的值變為負數
   result = -result;
  }
  return result;
 }
 
 /**
  * 計算括弧運算
  */
 private double parseBracket() throws Exception {
  double result; //結果
  //如果當前標記為左括弧,則表示是一個括弧運算
  if (this.token.equals("(")) {
   this.getToken(); //取下一標記
   result = this.parseAddOrSub(); //用加減法運算計運算元運算式的值
   //如果當前標記不等於右括弧,拋出括弧不匹配異常
   if (!this.token.equals(")")) {
    this.handleError(UNBALPARENS_ERROR);
   }
   this.getToken(); //否則取下一個標記
  } else {
   //如果不是左括弧,表示不是一個括弧運算,則用原子項目運算計運算元運算式值
   result = this.parseAtomElement();
  }
  return result;
 }
 
 /**
  * 計算原子項目運算,包括變數和數字
  */
 private double parseAtomElement() throws Exception {
  double result = 0.0; //結果
 
  switch(this.tokenType) {
  case NUMBER_TOKEN:
   //如果當前標記類型為數字
   try {
    //將數位字串轉換成數字值
    result = Double.parseDouble(this.token);
   } catch (NumberFormatException exc) {
    this.handleError(SYNTAX_ERROR);
   }
   this.getToken(); //取下一個標記
   break;
  case VARIABLE_TOKEN:
   //如果當前標記類型是變數,則取變數的值
   result = this.findVar(token);
   this.getToken();
   break;
  default:
   this.handleError(SYNTAX_ERROR);
   break;
  }
  return result;
 }
 
 /**
  * 根據變數名擷取變數的值,如果變數名長度大於1,則只取變數的第一個字元
  */
 private double findVar(String vname) throws Exception {
  if (!Character.isLetter(vname.charAt(0))) {
   this.handleError(SYNTAX_ERROR);
   return 0.0;
  }
  //從執行個體變數數組vars中取出該變數的值
  return vars[Character.toUpperCase(vname.charAt(0)) - ''A''];
 }
 
 /**
  * 復原,將解析器當前指標往前移到當前標記位置
  */
 private void putBack() {
  if (this.token == EOE) {
   return;
  }
  //解析器當前指標往前移動
  for (int i = 0; i < this.token.length(); i++ ){
   this.expIndex--;
  }
 }
 
 /**
  * 處理異常情況
  */
 private void handleError(int errorType) throws Exception {
  //遇到異常情況時,根據錯誤類型,取得異常提示資訊,將提示資訊封裝在異常中拋出
  throw new Exception(ERROR_MESSAGES[errorType]);
 }
 
 /**
  * 擷取下一個標記
  */
 private void getToken() {
  //設定初始值
  this.token = "";
  this.tokenType = NONE_TOKEN;
 
  //檢查運算式是否結束,如果解析器當前指標已經到達了字串長度,
  //則表明運算式已經結束,置當前標記的值為EOE
  if(this.expIndex == this.exp.length()) {
   this.token = EOE;
   return;
  }
 
  //跳過運算式中的空白符
  while (this.expIndex < this.exp.length()
    && Character.isWhitespace(this.exp.charAt(this.expIndex))) {
   ++this.expIndex;
  }
 
  //再次檢查運算式是否結束
  if (this.expIndex == this.exp.length()) {
   this.token = EOE;
   return;
  }
 
  //取得解析器當前指標指向的字元
  char currentChar = this.exp.charAt(this.expIndex);
  //如果當前字元是一個分隔字元,則認為這是一個分隔字元標記
  //給當前標記和標記類型賦值,並將指標後移
  if(isDelim(currentChar)) {
   this.token += currentChar;
   this.expIndex++;
   this.tokenType = DELIMITER_TOKEN;
  } else if (Character.isLetter(currentChar)) {
   //如果當前字元是一個字母,則認為是一個變數標記
   //將解析器指標往後移,知道遇到一個分隔字元,之間的字元都是變數的組成部分
   while(!isDelim(currentChar)) {
    this.token += currentChar;
    this.expIndex++;
    if(this.expIndex >= this.exp.length()) {
     break;
    } else {
     currentChar = this.exp.charAt(this.expIndex);
    }
   }
   this.tokenType = VARIABLE_TOKEN; //設定標記類型為變數
  } else if (Character.isDigit(currentChar)) {
   //如果當前字元是一個數字,則認為當前標記的類型為數字
   //將解析器指標後移,知道遇到一個分隔字元,之間的字元都是該數位組成部分
   while(!isDelim(currentChar)) {
    this.token += currentChar;
    this.expIndex++;
    if (this.expIndex >= this.exp.length()) {
     break;
    } else {
     currentChar = this.exp.charAt(this.expIndex);
    }
   }
   this.tokenType = NUMBER_TOKEN; //設定標記類型為數字
  } else {
   //無法識別的字元,則認為運算式結束
   this.token = EOE;
   return;
  }
 }
 
 /**
  * 判斷一個字元是否為分隔字元
  * 運算式中的字元包括:
  * 加“+”、減“-”、乘“*”、除“/”、模數“%”、指數“^”、賦值“=”、左括弧“(”、右括弧“)”
  */
 private boolean isDelim(char c) {
  if (("+-*/%^=()".indexOf(c) != -1))
   return true;
  return false;
 }
 /**
  * @param args
  */
 public static void main(String[] args) throws Exception{
  ExpressionParser test = new ExpressionParser();
 
  String exp1 = "a = 5.0";
  System.out.println("exp1(/"a = 5.0/") = " + test.evaluate(exp1));
 
  String exp2 = "b = 3.0";
  System.out.println("exp2(/"b = 3.0/") = " + test.evaluate(exp2));
 
  String exp3 = "(a + b) * (a - b)";
  System.out.println("exp3(/"(a + b) * (a - b)/") = " + test.evaluate(exp3));
 
  String exp4 = "3*5-4/2";
  System.out.println("exp4(/"3*5-4/2/") = " + test.evaluate(exp4));
 
  String exp5 = "(4-2) * ((a + b) / (a - b))";
  System.out.println("exp5(/"(4 - 2) * ((a + b) / (a - b))/") = " + test.evaluate(exp5));
 
  String exp6 = "5 % 2";
  System.out.println("exp6(/"5 % 2/") = " + test.evaluate(exp6));
 
  String exp7 = "3^2 * 5 + 4";
  System.out.println("exp7(/"3^2 * 5 + 4/") = " + test.evaluate(exp7));
 }
}
 

輸出結果:

exp1("a = 5.0") = 5.0
exp2("b = 3.0") = 3.0
exp3("(a + b) * (a - b)") = 16.0
exp4("3*5-4/2") = 13.0
exp5("(4 - 2) * ((a + b) / (a - b))") = 8.0
exp6("5 % 2") = 1.0
exp7("3^2 * 5 + 4") = 49.0

五、執行個體分析
    運算式的解析,實際就是一個運算式的分解過程。根據分隔字元將運算式分成若干段。然後計算每一段的值,最後都會歸結到一個原子運算式。

文章出處:http://www.diybl.com/course/3_program/java/javaxl/20071126/87573.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.