閱讀677 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  這是最近在項目中的一個需求,已知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));
    }

}


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

文章轉自莊周夢蝶  ,原文發布時間2007-08-06

最後更新:2017-05-18 10:32:30

  上一篇:go  《Spring 5官方文檔》11集成測試 (二)
  下一篇:go  Erlang ring benchmark