当前位置:  数据库>mysql

使用java处理字符串公式运算的方法

    来源: 互联网  发布时间:2014-10-08

    本文导语:    在改进一个关于合同的项目时,有个需求,就是由于合同中非数据项的计算公式会根据年份而进行变更,而之前是将公式硬编码到系统中的,只要时间一变,系统就没法使用了,因此要求合同中各个非基础数据的项都能自...

  在改进一个关于合同的项目时,有个需求,就是由于合同中非数据项的计算公式会根据年份而进行变更,而之前是将公式硬编码到系统中的,只要时间一变,系统就没法使用了,因此要求合同中各个非基础数据的项都能自定义公式,根据设置的公式来自动生成报表和合同中的数据。

  显然定义的公式都是以字符串来存储到数据库的,可是java中没有这种执行字符串公式的工具或者类,而且是公式可以嵌套一个中间公式。比如:基础数据dddd是56,而一个公式是依赖dddd的,eeee=dddd*20,而最终的公式可能是这样:eeee*-12+13-dddd+24。可知eeee是一个中间公式,所以一个公式的计算需要知道中间公式和基础数据。

这好像可以使用一个解释器模式来解决,但是我没有成功,因为括号的优先级是一个棘手的问题,后来又想到可以使用freemarker类似的模板引擎或者java6之后提供的ScriptEngine 脚本引擎,做了个实验,脚本引擎可以解决,但是这限制了必须使用java6及以上的版本。最终功夫不负有心人,终于找到了完美解决方案,即后缀表达式。我们平时写的公式称作中缀表达式,计算机处理起来比较困难,所以需要先将中缀表达式转换成计算机处理起来比较容易的后缀表达式。

将中缀表达式转换为后缀表达式具体算法规则:见后缀表达式


   a.若为 '(',入栈;

   b.若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ;

   c.若为 除括号外的其他运算符 ,当其优先级高于栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。

   ·当扫描的中缀表达式结束时,栈中的的所有运算符出栈; 

我们提出的要求设想是这样的:

代码如下:

public class FormulaTest {
     @Test
     public void testFormula() {
         //基础数据
         Map values = new HashMap();
         values.put("dddd", BigDecimal.valueOf(56d));

         //需要依赖的其他公式
         Map formulas = new HashMap();
         formulas.put("eeee", "#{dddd}*20");

         //需要计算的公式
         String expression = "#{eeee}*-12+13-#{dddd}+24";

         BigDecimal result = FormulaParser.parse(expression, formulas, values);
         Assert.assertEquals(result, BigDecimal.valueOf(-13459.0));
     }
 }

以下就是解决问题的步骤:

1、首先将所有中间变量都替换成基础数据

FormulaParser的finalExpression方法会将所有的中间变量都替换成基础数据,就是一个递归的做法

代码如下:

public class FormulaParser {
     /**
      * 匹配变量占位符的正则表达式
      */
     private static Pattern pattern = Pattern.compile("\#\{(.+?)\}");

     /**
      * 解析公式,并执行公式计算
      *
      * @param formula
      * @param formulas
      * @param values
      * @return
      */
     public static BigDecimal parse(String formula, Map formulas, Map values) {
         if (formulas == null)formulas = Collections.emptyMap();
         if (values == null)values = Collections.emptyMap();
         String expression = finalExpression(formula, formulas, values);
         return new Calculator().eval(expression);
     }

     /**
      * 解析公式,并执行公式计算
      *
      * @param formula
      * @param values
      * @return
      */
     public static BigDecimal parse(String formula, Map values) {
         if (values == null)values = Collections.emptyMap();
         return parse(formula, Collections. emptyMap(), values);
     }

     /**
      * 解析公式,并执行公式计算
      *
      * @param formula
      * @return
      */
     public static BigDecimal parse(String formula) {
         return parse(formula, Collections. emptyMap(), Collections. emptyMap());
     }

     /**
      * 将所有中间变量都替换成基础数据
      *
      * @param expression
      * @param formulas
      * @param values
      * @return
      */
     private static String finalExpression(String expression, Map formulas, Map values) {
         Matcher m = pattern.matcher(expression);
         if (!m.find())return expression;

         m.reset();

         StringBuffer buffer = new StringBuffer();
         while (m.find()) {
             String group = m.group(1);
             if (formulas != null && formulas.containsKey(group)) {
                 String formula = formulas.get(group);
                 m.appendReplacement(buffer, '(' + formula + ')');
             } else if (values != null && values.containsKey(group)) {
                 BigDecimal value = values.get(group);
                 m.appendReplacement(buffer,value.toPlainString());
             }else{
                 throw new IllegalArgumentException("expression '"+expression+"' has a illegal variable:"+m.group()+",cause veriable '"+group+"' not being found in formulas or in values.");
             }
         }
         m.appendTail(buffer);
         return finalExpression(buffer.toString(), formulas, values);
     }
 }

2、将中缀表达式转换为后缀表达式

  Calculator的infix2Suffix将中缀表达式转换成了后缀表达式

3、计算后缀表达式

  Calculator的evalInfix计算后缀表达式

代码如下:

public class Calculator{
     private static Log logger = LogFactory.getLog(Calculator.class);

     /**
      * 左括号
      */
     public final static char LEFT_BRACKET = '(';

     /**
      * 右括号
      */
     public final static char RIGHT_BRACKET = ')';

     /**
      * 中缀表达式中的空格,需要要忽略
      */
     public final static char BLANK = ' ';

     /**
      * 小数点符号
      */
     public final static char DECIMAL_POINT = '.';

     /**
      * 负号
      */
     public final static char NEGATIVE_SIGN = '-';

     /**
      * 正号
      */
     public final static char POSITIVE_SIGN = '+';

     /**
      * 后缀表达式的各段的分隔符
      */
     public final static char SEPARATOR = ' ';

     /**
      * 解析并计算表达式
      *
      * @param expression
      * @return
      */
     public BigDecimal eval(String expression) {
         String str = infix2Suffix(expression);
         logger.info("Infix Expression: " + expression);
         logger.info("Suffix Expression: " + str);
         if (str == null) {
             throw new IllegalArgumentException("Infix Expression is null!");
         }
         return evalInfix(str);
     }

     /**
      * 对后缀表达式进行计算
      *
      * @param expression
      * @return
      */
     private BigDecimal evalInfix(String expression) {
         String[] strs = expression.split("\s+");
         Stack stack = new Stack();
         for (int i = 0; i < strs.length; i++) {
             if (!Operator.isOperator(strs[i])) {
                 stack.push(strs[i]);
             } else {
                 Operator op = Operator.getInstance(strs[i]);
                 BigDecimal right =new BigDecimal(stack.pop());
                 BigDecimal left =new BigDecimal(stack.pop());
                 BigDecimal result = op.eval(left, right);
                 stack.push(String.valueOf(result));
             }
         }
         return new BigDecimal(stack.pop());
     }

     /**
      * 将中缀表达式转换为后缀表达式

      * 具体算法规则 81      * 1)计算机实现转换: 将中缀表达式转换为后缀表达式的算法思想:
      *     开始扫描;
      *         数字时,加入后缀表达式;
      *         运算符:
      *  a.若为 '(',入栈;
      *  b.若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ;
      *  c.若为 除括号外的其他运算符 ,当其优先级高于栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。
      *  ·当扫描的中缀表达式结束时,栈中的的所有运算符出栈; 
      *
      * @param expression
      * @return
      */
     public String infix2Suffix(String expression) {
         if (expression == null) return null;

         Stack stack = new Stack();

         char[] chs = expression.toCharArray();
         StringBuilder sb = new StringBuilder(chs.length);

         boolean appendSeparator = false;
         boolean sign = true;
         for (int i = 0; i < chs.length; i++) {
             char c = chs[i];

             // 空白则跳过
             if (c == BLANK)continue;

             // Next line is used output stack information.
             // System.out.printf("%-20s %s%n", stack, sb.toString());

             // 添加后缀表达式分隔符
             if (appendSeparator) {
                 sb.append(SEPARATOR);
                 appendSeparator = false;
             }

             if (isSign(c) && sign) {
                 sb.append(c);
             } else if (isNumber(c)) {
                 sign = false;// 数字后面不是正号或负号,而是操作符+-
                 sb.append(c);
             } else if (isLeftBracket(c)) {
                 stack.push(c);
             } else if (isRightBracket(c)) {
                 sign = false;

                 // 如果为),则弹出(上面的所有操作符,并添加到后缀表达式中,并弹出(
                 while (stack.peek() != LEFT_BRACKET) {
                     sb.append(SEPARATOR).append(stack.pop());
                 }
                 stack.pop();
             } else {
                 appendSeparator = true;
                 if (Operator.isOperator(c)) {
                     sign = true;

                     // 若为(则入栈
                     if (stack.isEmpty() || stack.peek() == LEFT_BRACKET) {
                         stack.push(c);
                         continue;
                     }
                     int precedence = Operator.getPrority(c);
                     while (!stack.isEmpty() && Operator.getPrority(stack.peek()) >= precedence) {
                         sb.append(SEPARATOR).append(stack.pop());
                     }
                     stack.push(c);
                 }
             }
         }
         while (!stack.isEmpty()) {
             sb.append(SEPARATOR).append(stack.pop());
         }
         return sb.toString();
     }

     /**
      * 判断某个字符是否是正号或者负号
      *
      * @param c
      * @return
      */
     private boolean isSign(char c) {
         return (c == NEGATIVE_SIGN || c == POSITIVE_SIGN);
     }

     /**
      * 判断某个字符是否为数字或者小数点
      *
      * @param c
      * @return
      */
     private boolean isNumber(char c) {
         return ((c >= '0' && c 1 ? false : isOperator(str.charAt(0));
     }

     /**
      * 根据运算符获得 Operator 实例
      *
      * @param c
      * @return 从注册中的 Operator 返回实例,尚未注册返回 null
      */
     public static Operator getInstance(char c) {
         return operators.get(c);
     }

     public static Operator getInstance(String str) {
         return str.length() > 1 ? null : getInstance(str.charAt(0));
     }

     /**
      * 根据操作数进行计算
      *
      * @param left
      *            左操作数
      * @param right
      *            右操作数
      * @return 计算结果
      */
     public abstract BigDecimal eval(BigDecimal left, BigDecimal right);


    
 
 

您可能感兴趣的文章:

  • Python不使用print而直接输出二进制字符串
  • 在ACC变成中要使用发ftp传送文件,但文件名不确定,请问怎么样在程序的FTP中使用字符串变量???
  • php使用strip_tags从字符串中去除html标记
  • 怎样在使用curses字符串输出函数或字符输出函数时,隐藏光标
  • 如何用sha1sum获取一个字符串使用sha-1加密后的16进制字符串?
  • 使用java如何分析系统不能识别的字符串?
  • 关于使用shell在文件中查找一段字符串的问题
  • 使用shell在文本文件中进行字符串搜索问题?shell高手请进,分不够可以再加
  • 使用sh脚本如何替换指定目录下所有文件中的指定字符串
  • Oracle中SQL语句连接字符串的符号使用介绍
  • c# split分隔字符串使用方法
  • 如何使用shell命令取到本机ip,注意只有本机ip的字符串 ,比如“xxx.xxx.xxx.xxx”
  • oracle使用instr或like方法判断是否包含字符串
  • 使用正则表达式匹配[***]样式的字符串
  • 浅析python 内置字符串处理函数的使用方法
  • c#字符串编码编码(encoding)使用方法示例
  • 我使用jdbc-odbc桥,拼出sql字符串,为什么总是说:括号内的串没有正常结束?
  • 使用T-SQL的Split拆分字符串的方法
  • c#使用htmlagilitypack解析html格式字符串
  • C#中使用Split方法拆分字符串实例
  • php使用正则表达式提取字符串中尖括号、小括号、中括号、大括号中的字符串
  • python的三目运算符和not in运算符使用示例
  • C++中的异或运算符^的使用方法
  • c# 空合并运算符“??”的使用详解
  • Ubuntu终端中使用expr(数字运算)不好用??
  • C# null 合并运算符??(双问号)使用示例
  • C++按位异或运算符的使用介绍
  • 使用BigDecimal进行精确运算(实现加减乘除运算)
  • C#中is与As运算符号的使用详解
  • Java instanceof 运算符的使用方法
  • sql集合运算符使用方法
  •  
    本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
    本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • 在Openoffice中如何使用公式编辑器?
  • C++ I/O 成员 tellg():使用输入流读取流指针
  • 在测试memset函数的执行效率时,分为使用Cash和不使用Cash辆种方式,该如何控制是否使用缓存?
  • C++ I/O 成员 tellp():使用输出流读取流指针
  • 求ibm6000的中文使用手册 !从来没用过服务器,现在急需使用它,不知如何使用! 急!!!!!
  • Office 2010 Module模式下使用VBA Addressof
  • 请问:在使用oracle数据库作开发时,是使用pro*c作开发好些,还是使用库函数如oci等好一些啊?或者它们有什么区别或者优缺点啊?
  • windows下tinyxml.dll下载安装使用(c++解析XML库)
  • 急求结果!!假设一个有两个元素的信号量集S,表示了一个磁带驱动器系统,其中进程1使用磁带机A,进程2同时使用磁带机A和B,进程3使用磁带机B。
  • tcmalloc内存泄露优化c++开源库下载,安装及使用介绍
  • c#中SAPI使用总结——SpVoice的使用方法
  • sharepoint 2010 使用STSNavigate函数实现文件下载举例
  • 使用了QWidget的程序,如何使用后台程序启动它?
  • 使用libpcap读取tcpdump抓取的文件并解析c代码实例
  • 共享内存一般是怎么使用的,是同消息队列配合使用么
  • c/c++预处理命令预#,##使用介绍
  • Jsp可否使用带有GUI的JavaBean,如何使用?
  • 在div中使用css让文字底部对齐的方法
  • asp程序使用的access在Linux下如何使用!
  • Python namedtuple(命名元组)使用实例
  • 新装的Linux使用root用户不能使用FTP?
  • MySQL Workbench的下载安装与使用教程
  • LINUX下使用Eclipse,如何使用交叉编译器?


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3