庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

java求字符串型逻辑表达式的bool值

Posted on 2007-08-06 12:37 dennis 阅读(7949) 评论(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.blogjava.net/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
兄弟,简单的看了一遍。没看的很细。就略微的和你交流一下吧。优先级考虑进去没有,复杂一些的逻辑表达式,带有括号和&& || 操作符的考虑进去没有。如果考虑进去了我拿来用用,不然还要用开源的。呵呵。

只有注册用户登录后才能发表评论。


网站导航: