首页 > 编程语言 >使用java处理字符串公式运算的方法

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

时间:2022-08-30 13:46:47浏览次数:70  
标签:return 运算 运算符 Operator static 字符串 java public BigDecimal

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

  显然定义的公式都是以字符串来存储到数据库的,可是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<String, BigDecimal> values = new HashMap<String, BigDecimal>();
         values.put("dddd", BigDecimal.valueOf(56d));

         //需要依赖的其他公式
         Map<String, String> formulas = new HashMap<String, String>();
         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<String, String> formulas, Map<String, BigDecimal> 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<String, BigDecimal> values) {
         if (values == null)values = Collections.emptyMap();
         return parse(formula, Collections.<String, String> emptyMap(), values);
     }

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

     /**
      * 将所有中间变量都替换成基础数据
      *
      * @param expression
      * @param formulas
      * @param values
      * @return
      */
     private static String finalExpression(String expression, Map<String, String> formulas, Map<String, BigDecimal> 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<String> stack = new Stack<String>();
         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());
     }

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

         Stack<Character> stack = new Stack<Character>();

         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 <= '9') || c == DECIMAL_POINT);
     }

     /**
      * 判断某个字符是否为左括号
      * 
      * @param c
      * @return
      */
     private boolean isLeftBracket(char c) {
         return c == LEFT_BRACKET;
     }

     /**
      * 判断某个字符是否为右括号
      * 
      * @param c
      * @return
      */
     private boolean isRightBracket(char c) {
         return c == RIGHT_BRACKET;
     }

  最后把操作符类贴上

View Code 
 public abstract class Operator {
     /**
      * 运算符
      */
     private char operator;

     /**
      * 运算符的优先级别,数字越大,优先级别越高
      */
     private int priority;

     private static Map<Character, Operator> operators = new HashMap<Character, Operator>();

     private Operator(char operator, int priority) {
         setOperator(operator);
         setPriority(priority);
         register(this);
     }

     private void register(Operator operator) {
         operators.put(operator.getOperator(), operator);
     }

     /**
      * 加法运算
      */
     public final static Operator ADITION = new Operator('+', 100) {
         public BigDecimal eval(BigDecimal left, BigDecimal right) {
             return left.add(right);
         }
     };

     /**
      * 减法运算
      */
     public final static Operator SUBTRATION = new Operator('-', 100) {
         public BigDecimal eval(BigDecimal left, BigDecimal right) {
             return left.subtract(right);
         }
     };

     /**
      * 乘法运算
      */
     public final static Operator MULTIPLICATION = new Operator('*', 200) {
         public BigDecimal eval(BigDecimal left, BigDecimal right) {
             return left.multiply(right);
         }
     };

     /**
      * 除法运算
      */
     public final static Operator DIVITION = new Operator('/', 200) {
         public BigDecimal eval(BigDecimal left, BigDecimal right) {
             return left.divide(right);
         }
     };

     /**
      * 冪运算
      */
     public final static Operator EXPONENT = new Operator('^', 300) {
         public BigDecimal eval(BigDecimal left, BigDecimal right) {
             return left.pow(right.intValue());
         }
     };

     public char getOperator() {
         return operator;
     }

     private void setOperator(char operator) {
         this.operator = operator;
     }

     public int getPriority() {
         return priority;
     }

     private void setPriority(int priority) {
         this.priority = priority;
     }

     /**
      * 根据某个运算符获得该运算符的优先级别
      * 
      * @param c
      * @return 运算符的优先级别
      */
     public static int getPrority(char c) {
         Operator op = operators.get(c);
         return op != null ? op.getPriority() : 0;
     }

     /**
      * 工具方法,判断某个字符是否是运算符
      * 
      * @param c
      * @return 是运算符返回 true,否则返回 false
      */
     public static boolean isOperator(char c) {
         return getInstance(c) != null;
     }

     public static boolean isOperator(String str) {
         return str.length() > 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);

  转自:使用java处理字符串公式运算的方法_Mysql_脚本之家 (jb51.net)

 

标签:return,运算,运算符,Operator,static,字符串,java,public,BigDecimal
From: https://www.cnblogs.com/wwssgg/p/16638977.html

相关文章

  • java类加载过程
    https://blog.csdn.net/weixin_37766296/article/details/80545283 https://www.cnblogs.com/wangwudi/p/12327942.html 类的加载顺序ClassLoader中默认的加载顺序......
  • Java包与Import导入
    包的概念包是Java语言提供的一种确保类名唯一性的机制,是类的一种组织和管理方式、是一组功能相似或相关的类或接口的集合。一个完整的类名是包名+类名,在没有import导入的......
  • Java 对象和类, 变量类型,构造方法,创建对象,实例,源文件申明规则
    Java作为一种面向对象语言。支持以下基本概念:多态继承封装抽象类对象实例方法重载对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为。例如,一条狗是一个......
  • Java常见错误总结
    1.Parameter0ofmethodmodifyResponseBodyGatewayFilterFactoryinorg.springframework.cloud.gateway.config.GatewayAutoConfigurationrequiredabeanoftype'o......
  • Java---泛型
    泛型出现的原因Java的泛型是在JDK1.5开始才加上的。在此之前的Java是没有泛型的。没有Java的泛型使用起来给人感觉非常的笨重,为了体会泛型带来的好处,来看看如果没有泛型......
  • 运算方法和运算器
    数据与文字的表示方法二进制....八进制......十六进制一般用数字0到9和字母A到F表示,其中:AF相当于十进制的1015八进制和十六进制主要目的:简化二进制的书写进制转化......
  • JAVA进阶--static、工具类、单例、继承--2022年8月28日
    第一节 static静态关键字1、成员变量的分类和访问分别是什么样的?静态成员变量(有static修饰,属于类,加载一次,可以被共享访问)访问格式:类名.变量......
  • java Map实体内容遍历后重建 map 空指针处理
    javaMap实体内容遍历后重建map空指针处理Map<String,Object>map=newHashMap<>();//NullPointerExceptionMap<String,Object>map1=map.entrySet()......
  • idea 编译 错误 Error:java: Compilation failed: internal java compiler error 解决
    手动修改(可能会被idea自动改回1.5)修改为对应的编译版本在工程的pom中添加如下配置<build><plugins><plugin><groupId>org.apache.maven.......
  • 力扣372(java)-超级次方(中等)
    题目:你的任务是计算 ab 对 1337取模,a是一个正整数,b是一个非常大的正整数且会以数组形式给出。示例1:输入:a=2,b=[3]输出:8示例2:输入:a=2,b=[1,0]输出:102......