莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          java求字符串型邏輯表達式的bool值

          Posted on 2007-08-06 12:37 dennis 閱讀(7973) 評論(7)  編輯  收藏 所屬分類: javamy open-source
              這是最近在項目中的一個需求,已知a=3,求字符串"a<=2"的值,也就是應該返回false。這個問題可大可小,就我們的應用場景也就是用來讓用戶自定義變量區間,比如類似下面這樣的規則:
          a<=2    返回積分系數1.0
          2<a<=5  返回積分系數1.1
          a>5     返回積分系數1.2

              如果用switch寫死在代碼中,以后要修改規則實在是很麻煩的事情,用戶也希望能自己維護這樣些區間值。于是我想就讓用戶自己輸入這樣的表達式和變量的值保存在數據庫中,然后計算的時候由系統來解析表達式并求值。問題就歸結到求值字符串型邏輯表達式。這個問題恰好是規則引擎的應用領域,可我們的系統已經上線蠻久了,從維護角度也不希望再引入新的開源工具,況且也就這么一個地方用到。如果是算術表達式(比如2+3之類)可以直接扔進數據庫執行即可,邏輯表達式倒是可以通過調用腳本語言來eval,但是同樣是考慮后期維護問題,也不想引入beanshell、groovy的腳本語言。所以,我就自己寫了個parser用于求值。
              基本原理就是維護兩個棧:操作數棧和操作符號棧,解析和求值的過程就是入棧和出棧操作。首先使用ArrayList實現一個棧,很容易的事情:
          class Stack {
              
          protected java.util.ArrayList pool = new java.util.ArrayList();

              
          public Stack() {
              }

              
          public Stack(int n) {
                  pool.ensureCapacity(n);
              }

              
          public void clear() {
                  pool.clear();
              }

              
          public boolean isEmpty() {
                  
          return pool.isEmpty();
              }

              
          public int size() {
                  
          return pool.size();
              }

              
          public Object topEl() {
                  
          if (isEmpty())
                      
          throw new java.util.EmptyStackException();
                  
          return pool.get(pool.size() - 1);
              }

              
          public Object pop() {
                  
          if (isEmpty())
                      
          throw new java.util.EmptyStackException();
                  
          return pool.remove(pool.size() - 1);
              }

              
          public void push(Object el) {
                  pool.add(el);
              }

              
          public String toString() {
                  
          return pool.toString();
              }
          }

              然后看看ExpressionParser.java,原理已經列上,注釋也有,使用了單例模式,就請自己看了:
          package net.rubyeye.codelib.util;

          /**
           * <p>類說明:用于表達式與實際值的比較</p>
           * <p>注意事項:</p>
           * <pre></pre>
           * <p>創建日期:Aug 6, 2007 10:18:58 AM</p>
           * <p>文件名:ExpressionParser.java</p>
           * 
          @author:莊曉丹
           * 
          @version $Id:$
           
          */
          public class ExpressionParser {
              
          private static final boolean DEBUG = true;

              
          private static ExpressionParser parser = new ExpressionParser();

              
          private ExpressionParser() {

              }

              
          public static ExpressionParser getInstance() {
                  
          return parser;
              }

              
          public boolean fireRule(String expression, double fact) {
                  traceCalculate(
          "\nexpression:" + expression);
                  expression 
          = expression.replace("\n|\r""").trim();
                  
          char[] chars = expression.toCharArray();
                  
          return parseExpression(fact, chars);
              }

              
          /**
               * 
          @param fact
               * 
          @param operatorsStack
               * 
          @param operandsStack
               * 
          @param chars
               * 
          @param operand
               * 
          @param operator
               * 
          @return
               
          */
              
          private boolean parseExpression(double fact, char[] chars) {
                  
          boolean result = true;
                  String operand 
          = "";
                  String operator 
          = "";
                  Stack operatorsStack 
          = new Stack();
                  Stack operandsStack 
          = new Stack();
                  
          for (int i = 0; i < chars.length; i++) {
                      
          char token = chars[i];
                      traceCalculate(
          "token:" + token);
                      
          if (Character.isDigit(token) || token == '.') {
                          
          if (!operator.equals("")) {
                              traceCalculate(
          "push operator:" + operator);
                              
          //    將操作符放入操作符號棧
                              operatorsStack.push(operator);
                              operator 
          = "";

                          }
                          operand 
          += token;
                          result 
          = checkTail(fact, operatorsStack, operandsStack,
                                  chars.length, operand, result, i);
                          
          continue;
                      } 
          else if (Character.isLetter(token)) {
                          
          if (!operator.equals("")) {
                              traceCalculate(
          "push operator:" + operator);
                              
          //    將操作符放入操作符號棧
                              operatorsStack.push(operator);
                              operator 
          = "";
                          }
                          operand 
          = String.valueOf(token);
                          result 
          = checkTail(fact, operatorsStack, operandsStack,
                                  chars.length, operand, result, i);
                          
          //將操作數放入操作數棧
                          operandsStack.push(operand);
                          traceCalculate(
          "push operand:" + token);
                          operand 
          = "";
                          
          continue;
                      } 
          else {
                          
          if (!operatorsStack.isEmpty() && !operandsStack.isEmpty()) {
                              
          //當前操作數是字母(變量),已存入棧,因此需要取出
                              if (operand.equals("")) {
                                  operand 
          = (String) operandsStack.pop();
                                  result 
          = result
                                          
          && calculatePerfomance(operatorsStack,
                                                  operandsStack, operand, fact);
                                  
          //當前操作數是數字    
                              } else {
                                  result 
          = result
                                          
          && calculatePerfomance(operatorsStack,
                                                  operandsStack, operand, fact);

                              }
                          }

                          
          if (!operand.equals("")) {
                              result 
          = checkTail(fact, operatorsStack, operandsStack,
                                      chars.length, operand, result, i);
                              
          //將操作數放入操作數棧
                              operandsStack.push(operand);
                              traceCalculate(
          "push2 operand:" + operand);
                              operand 
          = "";
                          }

                          operator 
          += token;
                          
          continue;
                      }

                  }
                  
          return result;
              }

              
          /**
               * 判斷是否已經到表達式尾端,如果是,計算
               * 
          @param fact
               * 
          @param operatorsStack
               * 
          @param operandsStack
               * 
          @param chars
               * 
          @param operand
               * 
          @param result
               * 
          @param i
               * 
          @return
               
          */
              
          private boolean checkTail(double fact, Stack operatorsStack,
                      Stack operandsStack, 
          int chars_length, String operand,
                      
          boolean result, int i) {
                  
          if (i == chars_length - 1) {
                      result 
          = result
                              
          && calculatePerfomance(operatorsStack, operandsStack,
                                      operand, fact);
                  }
                  
          return result;
              }

              
          private void displayStack(String name,Stack stack) {
                  
          if (DEBUG) {
                      
          for (int i = 0; i < stack.pool.size(); i++)
                          System.out.println(name
          +stack.pool.get(i));
                  }
              }

              
          private boolean calculatePerfomance(Stack operatorsStack,
                      Stack operandsStack, String currentOperand, 
          double fact) {
                  traceCalculate(
          "開始計算");
                  displayStack(
          "operators stack:",operatorsStack);
                  displayStack(
          "operands stack:",operandsStack);
                  traceCalculate(
          "currentOperand=" + currentOperand);
                  String operator 
          = (String) operatorsStack.pop();
                  
          double lastOperand = coverOperandToDouble((String) operandsStack.pop(),
                          fact);
                  
          double nextOperand = coverOperandToDouble(currentOperand, fact);
                  
          boolean result = true;
                  
          if (operator.equals("=="))
                      
          return lastOperand == nextOperand;
                  if (operator.indexOf("=") >= 0)
                      hasEqual = true;
                  
          char[] operators = operator.toCharArray();
                  
          for (int i = 0; i < operators.length; i++) {
                      
          switch (operators[i]) {
                      
          case '<':
                          result 
          = result && (lastOperand < nextOperand);
                          
          break;
                      
          case '=':
                          
          //result為false,也就是小于,大于符號不滿足的時候,判斷等號是否成立
                          if (!result)
                              result 
          = (lastOperand == nextOperand);
                          
          break;
                      
          case '>':
                          result 
          = result && (lastOperand > nextOperand);
                          
          break;
                      }
                  }
                  if ((!result) && hasEqual)
                      result = lastOperand == nextOperand;
                  
          return result;

              }

              
          /**
               * 用于debug
               
          */
              
          private void traceCalculate(String info) {
                  
          if (DEBUG)
                      System.out.println(info);
              }

              
          private double coverOperandToDouble(String operand, double fact) {
                  
          //如果是字母,也就是變量,返回fact變量
                  if (Character.isLetter(operand.toCharArray()[0]))
                      
          return fact;
                  
          else
                      
          return Double.parseDouble(operand);
              }
          }
              通過DEBUG變量來決定是否輸出計算過程,你可以設置為true來看看某個表達式的計算過程。附上單元測試來看看怎么用:
          package net.rubyeye.codelib.util;

          /**
           * 測試表達式計算
           
          */
          import junit.framework.TestCase;

          public class ExpressionCalculateTest extends TestCase {
              String exp1,exp2,exp3, exp4;
              
          double v1, v2, v3, v4, v5;

              ExpressionParser parser 
          = null;

              
          protected void setUp() throws Exception {
                  exp1 
          = "a<80";
                  exp2 
          = "80<=a<81";
                  exp3 
          = "81<=a<=82";
                  exp4 
          = "a>=90";
                  v1 
          = 70.0;
                  v2 
          = 81.2;
                  v3 
          = 80;
                  v4 
          = 90;
                  v5 
          = 92;
                  parser 
          = ExpressionParser.getInstance();
              }

              
          public void testFireRule() throws Exception {
                  assertFalse(parser.fireRule(exp1, v4));
                  assertTrue(parser.fireRule(exp1, v1));
                  assertFalse(parser.fireRule(exp1, v3));
                  assertFalse(parser.fireRule(exp2, v2));
                  assertTrue(parser.fireRule(exp2, v3));
                  assertFalse(parser.fireRule(exp2, 
          82));
                  assertTrue(parser.fireRule(exp3, v2));
                  assertTrue(parser.fireRule(exp4, v4));
                  assertFalse(parser.fireRule(exp4, v1));
                  assertTrue(parser.fireRule(exp4, v5));
                  assertTrue(parser.fireRule(
          "b==100.00"100.0));
                  assertTrue(parser.fireRule(
          "c==0.00"0));
                  assertTrue(parser.fireRule(
          "60<=c<=80"79.9));
                  assertFalse(parser.fireRule(
          "60<=50<=80"0.0));
                  assertTrue(parser.fireRule(
          "60<=79<=80"0.0));
                  assertFalse(parser.fireRule(
          "60<=99<=80"0.0));
                  
                  assertTrue(parser.fireRule(
          "60<=80<=90<100"0.0));
                  assertFalse(parser.fireRule(
          "60<=99<=80<100"0.0));
                  assertTrue(parser.fireRule("10=<a=<30", 25));
              }

          }

              這個小程序對處理一般的類似區間的規則計算應該還有點用,希望對別人幫助吧。表達式中的邏輯運算符>=和<=可以用=>和=<替代。


          評論

          # re: java求值字符串型邏輯表達式  回復  更多評論   

          2007-08-06 17:04 by Scott.Pan
          看了一遍,不是很明白意在講什么,不過感覺寫的應該挺不錯.

          # re: java求值字符串型邏輯表達式  回復  更多評論   

          2007-08-06 17:27 by dennis
          @Scott.Pan
          汗,看來我的表達能力有問題
          其實就是一個解析邏輯表達式的程序,比如字符串”60<=a<81"
          當a=71的時候,這個字符串執行的結果應該是true,就是用來計算這個的。

          # re: java求字符串型邏輯表達式的bool值  回復  更多評論   

          2007-08-07 09:29 by dreamstone
          呵呵,數據結構的練習啊。 jdk里邊有stack的實現,可以直接使用.

          # re: java求字符串型邏輯表達式的bool值  回復  更多評論   

          2007-08-07 09:54 by dennis
          @dreamstone
          我記的java里的Stack繼承自Vector,多了一大堆不必要的方法,而且是線程安全的吧。我這里只是個局部變量。棧的實現并不是主題。

          # re: java求字符串型邏輯表達式的bool值  回復  更多評論   

          2008-03-13 11:07 by 曲強 Nicky
          可以參考
          http://www.aygfsteel.com/wqnashqu/archive/2008/02/26/182285.html
          使用樹遍歷,當然腳本也是可選方式之一。

          # re: java求字符串型邏輯表達式的bool值  回復  更多評論   

          2008-03-14 10:44 by dennis
          @曲強 Nicky
          這個謝謝,很早寫的東西了,沒想到還有人關注。呵呵,當時寫的很不成熟,也沒有去找找開源工具,見笑了。目前早已離開那家公司,那個功能當時也是滿足客戶要求的。做這個方法很多,本質上就是一個詞法解析和分析過程,可以利用各種parser generator。

          # re: java求字符串型邏輯表達式的bool值  回復  更多評論   

          2012-01-29 23:05 by whp
          兄弟,簡單的看了一遍。沒看的很細。就略微的和你交流一下吧。優先級考慮進去沒有,復雜一些的邏輯表達式,帶有括號和&& || 操作符的考慮進去沒有。如果考慮進去了我拿來用用,不然還要用開源的。呵呵。
          主站蜘蛛池模板: 苍南县| 临夏市| 平湖市| 洛阳市| 明星| 宁国市| 彭山县| 江达县| 积石山| 库尔勒市| 磴口县| 布尔津县| 正蓝旗| 宕昌县| 南溪县| 通榆县| 塔城市| 三门峡市| 康定县| 开原市| 保山市| 博爱县| 同仁县| 乌海市| 荥阳市| 茶陵县| 南涧| 和顺县| 新蔡县| 班戈县| 彭水| 隆尧县| 太湖县| 宿松县| 舒城县| 镶黄旗| 玛沁县| 金阳县| 鲁山县| 冀州市| 库车县|