莊周夢蝶

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

          java求字符串型邏輯表達(dá)式的bool值

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

              如果用switch寫死在代碼中,以后要修改規(guī)則實(shí)在是很麻煩的事情,用戶也希望能自己維護(hù)這樣些區(qū)間值。于是我想就讓用戶自己輸入這樣的表達(dá)式和變量的值保存在數(shù)據(jù)庫中,然后計(jì)算的時候由系統(tǒng)來解析表達(dá)式并求值。問題就歸結(jié)到求值字符串型邏輯表達(dá)式。這個問題恰好是規(guī)則引擎的應(yīng)用領(lǐng)域,可我們的系統(tǒng)已經(jīng)上線蠻久了,從維護(hù)角度也不希望再引入新的開源工具,況且也就這么一個地方用到。如果是算術(shù)表達(dá)式(比如2+3之類)可以直接扔進(jìn)數(shù)據(jù)庫執(zhí)行即可,邏輯表達(dá)式倒是可以通過調(diào)用腳本語言來eval,但是同樣是考慮后期維護(hù)問題,也不想引入beanshell、groovy的腳本語言。所以,我就自己寫了個parser用于求值。
              基本原理就是維護(hù)兩個棧:操作數(shù)棧和操作符號棧,解析和求值的過程就是入棧和出棧操作。首先使用ArrayList實(shí)現(xiàn)一個棧,很容易的事情:
          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,原理已經(jīng)列上,注釋也有,使用了單例模式,就請自己看了:
          package net.rubyeye.codelib.util;

          /**
           * <p>類說明:用于表達(dá)式與實(shí)際值的比較</p>
           * <p>注意事項(xiàng):</p>
           * <pre></pre>
           * <p>創(chuàng)建日期: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);
                          
          //將操作數(shù)放入操作數(shù)棧
                          operandsStack.push(operand);
                          traceCalculate(
          "push operand:" + token);
                          operand 
          = "";
                          
          continue;
                      } 
          else {
                          
          if (!operatorsStack.isEmpty() && !operandsStack.isEmpty()) {
                              
          //當(dāng)前操作數(shù)是字母(變量),已存入棧,因此需要取出
                              if (operand.equals("")) {
                                  operand 
          = (String) operandsStack.pop();
                                  result 
          = result
                                          
          && calculatePerfomance(operatorsStack,
                                                  operandsStack, operand, fact);
                                  
          //當(dāng)前操作數(shù)是數(shù)字    
                              } else {
                                  result 
          = result
                                          
          && calculatePerfomance(operatorsStack,
                                                  operandsStack, operand, fact);

                              }
                          }

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

                          operator 
          += token;
                          
          continue;
                      }

                  }
                  
          return result;
              }

              
          /**
               * 判斷是否已經(jīng)到表達(dá)式尾端,如果是,計(jì)算
               * 
          @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(
          "開始計(jì)算");
                  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變量來決定是否輸出計(jì)算過程,你可以設(shè)置為true來看看某個表達(dá)式的計(jì)算過程。附上單元測試來看看怎么用:
          package net.rubyeye.codelib.util;

          /**
           * 測試表達(dá)式計(jì)算
           
          */
          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));
              }

          }

              這個小程序?qū)μ幚硪话愕念愃茀^(qū)間的規(guī)則計(jì)算應(yīng)該還有點(diǎn)用,希望對別人幫助吧。表達(dá)式中的邏輯運(yùn)算符>=和<=可以用=>和=<替代。


          評論

          # re: java求值字符串型邏輯表達(dá)式  回復(fù)  更多評論   

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

          # re: java求值字符串型邏輯表達(dá)式  回復(fù)  更多評論   

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

          # re: java求字符串型邏輯表達(dá)式的bool值  回復(fù)  更多評論   

          2007-08-07 09:29 by dreamstone
          呵呵,數(shù)據(jù)結(jié)構(gòu)的練習(xí)啊。 jdk里邊有stack的實(shí)現(xiàn),可以直接使用.

          # re: java求字符串型邏輯表達(dá)式的bool值  回復(fù)  更多評論   

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

          # re: java求字符串型邏輯表達(dá)式的bool值  回復(fù)  更多評論   

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

          # re: java求字符串型邏輯表達(dá)式的bool值  回復(fù)  更多評論   

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

          # re: java求字符串型邏輯表達(dá)式的bool值  回復(fù)  更多評論   

          2012-01-29 23:05 by whp
          兄弟,簡單的看了一遍。沒看的很細(xì)。就略微的和你交流一下吧。優(yōu)先級考慮進(jìn)去沒有,復(fù)雜一些的邏輯表達(dá)式,帶有括號和&& || 操作符的考慮進(jìn)去沒有。如果考慮進(jìn)去了我拿來用用,不然還要用開源的。呵呵。
          主站蜘蛛池模板: 新平| 徐水县| 民勤县| 澎湖县| 岳阳市| 遵义县| 黄浦区| 峡江县| 东方市| 砚山县| 桐梓县| 大余县| 木兰县| 二连浩特市| 尼玛县| 神农架林区| 武宣县| 津市市| 大城县| 大厂| 婺源县| 昆明市| 灵璧县| 库车县| 宁武县| 简阳市| 岳普湖县| 冷水江市| 西宁市| 隆德县| 武安市| 临西县| 鹰潭市| 建阳市| 西贡区| 丰镇市| 柘荣县| 探索| 锡林浩特市| 花莲市| 石阡县|