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

时间: 2025-01-27 14:29:50

java求字符串型逻辑表达式的bool值的相关文章

我的Java开发学习之旅------&amp;gt;工具类:Java获取字符串和文件进行MD5值

ps:这几天本人用百度云盘秒传了几部大片到云盘上,几个G的文件瞬秒竟然显示"上传成功"!这真让我目瞪口呆,要是这样的话,那得多快的网速,这绝对是不可能的,也许这仅是个假象.百度了一下才发现所谓的"秒传"是常见的"忽略式"上传方式,就是您上传了一个文件名为111.exe,MD5为一个数,有一个网友以前也上传一个叫222.exe,MD5和您上传的文件MD5码一模一样,所以这个文件上传到服务器上的时间就很短了,这是因为别人上传过这个文件,您上传这个文件

java获得字符串间运算符变量的值

例如:Stirng str="a+b*c/d"; 获取出a,b,c,d放入数组中   import java.util.regex.Matcher; import java.util.regex.Pattern;  public class Test {    public static void main(String[] args){   String str="a+b*c/d";    Pattern p=Pattern.compile("[a-z]

java截断字符串问题,用逗号截断,但是值里面有逗号

问题描述 java截断字符串问题,用逗号截断,但是值里面有逗号 一个SQL语句的values里面的值,这里是三个字段,null,'0,1,2,3','2014-03-12 18:38:26.467319' 现在的问题是用逗号截断,第二个字段的值'0,1,2,3'也会被截断,求解决方法,怎么截断成三个 串 null 0,1,2,3 2014-03-12 18:38:26.467319 解决方案 public static void main(String[] args) { String s =

java字符串-java 求n个长度的字符串取出m个长度的组合

问题描述 java 求n个长度的字符串取出m个长度的组合 java 求n个长度的字符串取出m个长度的组合 求高手指点!!!!!多谢

求字符串的len组合数(java程序)

import java.util.List; import java.util.ArrayList; /** * 求字符串的len组合数 * * @author wenin819 * */ public class Combination{ /** * 求组合数的主要方法 */ public static List<String> combination(String inStr, int len){ StringBuffer noDoubleStr =new StringBuffer(inS

求大神解答关于java string字符串的问题,现有如下要截取,希望按括号分组

问题描述 求大神解答关于java string字符串的问题,现有如下要截取,希望按括号分组 如下字符串:希望每个括号之间的内容分一组变成子字符串,或者输出到文本每个括号之间的内容为一行.丢弃其余的部分 CF --> ('c1,t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t i t

求字符串全排列的递归算法(java程序)

import java.util.ArrayList; import java.util.List; /** * 求字符串的全排列 * * @author wenin819 * */ public class Arrange { /** * 判断调用求排列的主要方法 */ public static List<String> arrange(String input){ if(null == input || 0 == input.length()){ System.out.println(&

字符串-java中String究竟是传递的值,还是引用

问题描述 java中String究竟是传递的值,还是引用 很多人说,java中的String虽然是对象,但是具有值类型的特征.他是值传递,传递的值. 我认为这这种说法完全是错误的. String和普通对象毫无区别,和其他对象一样,传递的是引用,而不是值. 一段String的运用代码: String a = "123"; public void change(String a){ a = "234"; } 很多人会说,a的值没有变,是因为String是值传递,问题是,

查询 文件读写-java程序字符串问题(求大牛帮忙)

问题描述 java程序字符串问题(求大牛帮忙) 一个文件夹中(可能包含多个文本文件或子文件夹),查询其中包含关键字(至少一个)的文本文件然后输出这个文本文件的路径和所包含的关键字.关键字也存放在一个文本文件当中.不知程序该怎么写(java菜鸟,求大牛帮忙).关键字不区分大小写,一般只有字母和ASCII码组成,要忽略空格.最后的结果也要输出在一个文本文件上 解决方案 这个的话 第一个就这么个思路了 1:写一个递归算法 遍历到当前文件夹下的所有子文件(包括文件夹) 如果是文本文件的话则调用查找关键字