java处理字符串公式运算

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

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

 

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

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

   a.若为 '(',入栈;

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

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

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

 

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

 1 public class FormulaTest {
 2     @Test
 3     public void testFormula() {
 4         //基础数据
 5         Map<String, BigDecimal> values = new HashMap<String, BigDecimal>();
 6         values.put("dddd", BigDecimal.valueOf(56d));
 7 
 8         //需要依赖的其他公式
 9         Map<String, String> formulas = new HashMap<String, String>();
10         formulas.put("eeee", "#{dddd}*20");
11 
12         //需要计算的公式
13         String expression = "#{eeee}*-12+13-#{dddd}+24";
14         
15         BigDecimal result = FormulaParser.parse(expression, formulas, values);
16         Assert.assertEquals(result, BigDecimal.valueOf(-13459.0));
17     }
18 }

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

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

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

 1 public class FormulaParser {
 2     /**
 3      * 匹配变量占位符的正则表达式
 4      */
 5     private static Pattern pattern = Pattern.compile("\\#\\{(.+?)\\}");
 6 
 7     /**
 8      * 解析公式,并执行公式计算
 9      * 
10      * @param formula
11      * @param formulas
12      * @param values
13      * @return
14      */
15     public static BigDecimal parse(String formula, Map<String, String> formulas, Map<String, BigDecimal> values) {
16         if (formulas == null)formulas = Collections.emptyMap();
17         if (values == null)values = Collections.emptyMap();
18         String expression = finalExpression(formula, formulas, values);
19         return new Calculator().eval(expression);
20     }
21 
22     /**
23      * 解析公式,并执行公式计算
24      * 
25      * @param formula
26      * @param values
27      * @return
28      */
29     public static BigDecimal parse(String formula, Map<String, BigDecimal> values) {
30         if (values == null)values = Collections.emptyMap();
31         return parse(formula, Collections.<String, String> emptyMap(), values);
32     }
33 
34     /**
35      * 解析公式,并执行公式计算
36      * 
37      * @param formula
38      * @return
39      */
40     public static BigDecimal parse(String formula) {
41         return parse(formula, Collections.<String, String> emptyMap(), Collections.<String, BigDecimal> emptyMap());
42     }
43 
44     /**
45      * 将所有中间变量都替换成基础数据
46      * 
47      * @param expression
48      * @param formulas
49      * @param values
50      * @return
51      */
52     private static String finalExpression(String expression, Map<String, String> formulas, Map<String, BigDecimal> values) {
53         Matcher m = pattern.matcher(expression);
54         if (!m.find())return expression;
55 
56         m.reset();
57 
58         StringBuffer buffer = new StringBuffer();
59         while (m.find()) {
60             String group = m.group(1);
61             if (formulas != null && formulas.containsKey(group)) {
62                 String formula = formulas.get(group);
63                 m.appendReplacement(buffer, '(' + formula + ')');
64             } else if (values != null && values.containsKey(group)) {
65                 BigDecimal value = values.get(group);
66                 m.appendReplacement(buffer,value.toPlainString());
67             }else{
68                 throw new IllegalArgumentException("expression '"+expression+"' has a illegal variable:"+m.group()+",cause veriable '"+group+"' not being found in formulas or in values.");
69             }
70         }
71         m.appendTail(buffer);
72         return finalExpression(buffer.toString(), formulas, values);
73     }
74 }

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

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

3、计算后缀表达式

  Calculator的evalInfix计算后缀表达式

  1 public class Calculator{
  2     private static Log logger = LogFactory.getLog(Calculator.class);
  3 
  4     /**
  5      * 左括号
  6      */
  7     public final static char LEFT_BRACKET = '(';
  8 
  9     /**
 10      * 右括号
 11      */
 12     public final static char RIGHT_BRACKET = ')';
 13 
 14     /**
 15      * 中缀表达式中的空格,需要要忽略
 16      */
 17     public final static char BLANK = ' ';
 18 
 19     /**
 20      * 小数点符号
 21      */
 22     public final static char DECIMAL_POINT = '.';
 23 
 24     /**
 25      * 负号
 26      */
 27     public final static char NEGATIVE_SIGN = '-';
 28 
 29     /**
 30      * 正号
 31      */
 32     public final static char POSITIVE_SIGN = '+';
 33 
 34     /**
 35      * 后缀表达式的各段的分隔符
 36      */
 37     public final static char SEPARATOR = ' ';
 38     
 39     /**
 40      * 解析并计算表达式
 41      * 
 42      * @param expression
 43      * @return
 44      */
 45     public BigDecimal eval(String expression) {
 46         String str = infix2Suffix(expression);
 47         logger.info("Infix Expression: " + expression);
 48         logger.info("Suffix Expression: " + str);
 49         if (str == null) {
 50             throw new IllegalArgumentException("Infix Expression is null!");
 51         }
 52         return evalInfix(str);
 53     }
 54 
 55     /**
 56      * 对后缀表达式进行计算
 57      * 
 58      * @param expression
 59      * @return
 60      */
 61     private BigDecimal evalInfix(String expression) {
 62         String[] strs = expression.split("\\s+");
 63         Stack<String> stack = new Stack<String>();
 64         for (int i = 0; i < strs.length; i++) {
 65             if (!Operator.isOperator(strs[i])) {
 66                 stack.push(strs[i]);
 67             } else {
 68                 Operator op = Operator.getInstance(strs[i]);
 69                 BigDecimal right =new BigDecimal(stack.pop());
 70                 BigDecimal left =new BigDecimal(stack.pop());
 71                 BigDecimal result = op.eval(left, right);
 72                 stack.push(String.valueOf(result));
 73             }
 74         }
 75         return new BigDecimal(stack.pop());
 76     }
 77 
 78     /**
 79      * 将中缀表达式转换为后缀表达式<br>
 80      * 具体算法规则 81      * 1)计算机实现转换: 将中缀表达式转换为后缀表达式的算法思想: 
 82      *     开始扫描; 
 83      *         数字时,加入后缀表达式; 
 84      *         运算符: 
 85      *  a.若为 '(',入栈;
 86      *  b.若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ; 
 87      *  c.若为 除括号外的其他运算符 ,当其优先级高于栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。
 88      *  ·当扫描的中缀表达式结束时,栈中的的所有运算符出栈; 
 89      * 
 90      * @param expression
 91      * @return
 92      */
 93     public String infix2Suffix(String expression) {
 94         if (expression == null) return null;
 95 
 96         Stack<Character> stack = new Stack<Character>();
 97 
 98         char[] chs = expression.toCharArray();
 99         StringBuilder sb = new StringBuilder(chs.length);
100 
101         boolean appendSeparator = false;
102         boolean sign = true;
103         for (int i = 0; i < chs.length; i++) {
104             char c = chs[i];
105 
106             // 空白则跳过
107             if (c == BLANK)continue;
108 
109             // Next line is used output stack information.
110             // System.out.printf("%-20s %s%n", stack, sb.toString());
111 
112             // 添加后缀表达式分隔符
113             if (appendSeparator) {
114                 sb.append(SEPARATOR);
115                 appendSeparator = false;
116             }
117 
118             if (isSign(c) && sign) {
119                 sb.append(c);
120             } else if (isNumber(c)) {
121                 sign = false;// 数字后面不是正号或负号,而是操作符+-
122                 sb.append(c);
123             } else if (isLeftBracket(c)) {
124                 stack.push(c);
125             } else if (isRightBracket(c)) {
126                 sign = false;
127 
128                 // 如果为),则弹出(上面的所有操作符,并添加到后缀表达式中,并弹出(
129                 while (stack.peek() != LEFT_BRACKET) {
130                     sb.append(SEPARATOR).append(stack.pop());
131                 }
132                 stack.pop();
133             } else {
134                 appendSeparator = true;
135                 if (Operator.isOperator(c)) {
136                     sign = true;
137 
138                     // 若为(则入栈
139                     if (stack.isEmpty() || stack.peek() == LEFT_BRACKET) {
140                         stack.push(c);
141                         continue;
142                     }
143                     int precedence = Operator.getPrority(c);
144                     while (!stack.isEmpty() && Operator.getPrority(stack.peek()) >= precedence) {
145                         sb.append(SEPARATOR).append(stack.pop());
146                     }
147                     stack.push(c);
148                 }
149             }
150         }
151         while (!stack.isEmpty()) {
152             sb.append(SEPARATOR).append(stack.pop());
153         }
154         return sb.toString();
155     }
156 
157     /**
158      * 判断某个字符是否是正号或者负号
159      * 
160      * @param c
161      * @return
162      */
163     private boolean isSign(char c) {
164         return (c == NEGATIVE_SIGN || c == POSITIVE_SIGN);
165     }
166 
167     /**
168      * 判断某个字符是否为数字或者小数点
169      * 
170      * @param c
171      * @return
172      */
173     private boolean isNumber(char c) {
174         return ((c >= '0' && c <= '9') || c == DECIMAL_POINT);
175     }
176 
177     /**
178      * 判断某个字符是否为左括号
179      * 
180      * @param c
181      * @return
182      */
183     private boolean isLeftBracket(char c) {
184         return c == LEFT_BRACKET;
185     }
186 
187     /**
188      * 判断某个字符是否为右括号
189      * 
190      * @param c
191      * @return
192      */
193     private boolean isRightBracket(char c) {
194         return c == RIGHT_BRACKET;
195     }

最后把操作符类贴上

View Code
  1 public abstract class Operator {
  2     /**
  3      * 运算符
  4      */
  5     private char operator;
  6 
  7     /**
  8      * 运算符的优先级别,数字越大,优先级别越高
  9      */
 10     private int priority;
 11 
 12     private static Map<Character, Operator> operators = new HashMap<Character, Operator>();
 13 
 14     private Operator(char operator, int priority) {
 15         setOperator(operator);
 16         setPriority(priority);
 17         register(this);
 18     }
 19 
 20     private void register(Operator operator) {
 21         operators.put(operator.getOperator(), operator);
 22     }
 23 
 24     /**
 25      * 加法运算
 26      */
 27     public final static Operator ADITION = new Operator('+', 100) {
 28         public BigDecimal eval(BigDecimal left, BigDecimal right) {
 29             return left.add(right);
 30         }
 31     };
 32 
 33     /**
 34      * 减法运算
 35      */
 36     public final static Operator SUBTRATION = new Operator('-', 100) {
 37         public BigDecimal eval(BigDecimal left, BigDecimal right) {
 38             return left.subtract(right);
 39         }
 40     };
 41 
 42     /**
 43      * 乘法运算
 44      */
 45     public final static Operator MULTIPLICATION = new Operator('*', 200) {
 46         public BigDecimal eval(BigDecimal left, BigDecimal right) {
 47             return left.multiply(right);
 48         }
 49     };
 50 
 51     /**
 52      * 除法运算
 53      */
 54     public final static Operator DIVITION = new Operator('/', 200) {
 55         public BigDecimal eval(BigDecimal left, BigDecimal right) {
 56             return left.divide(right);
 57         }
 58     };
 59 
 60     /**
 61      * 冪运算
 62      */
 63     public final static Operator EXPONENT = new Operator('^', 300) {
 64         public BigDecimal eval(BigDecimal left, BigDecimal right) {
 65             return left.pow(right.intValue());
 66         }
 67     };
 68 
 69     public char getOperator() {
 70         return operator;
 71     }
 72 
 73     private void setOperator(char operator) {
 74         this.operator = operator;
 75     }
 76 
 77     public int getPriority() {
 78         return priority;
 79     }
 80 
 81     private void setPriority(int priority) {
 82         this.priority = priority;
 83     }
 84 
 85     /**
 86      * 根据某个运算符获得该运算符的优先级别
 87      * 
 88      * @param c
 89      * @return 运算符的优先级别
 90      */
 91     public static int getPrority(char c) {
 92         Operator op = operators.get(c);
 93         return op != null ? op.getPriority() : 0;
 94     }
 95 
 96     /**
 97      * 工具方法,判断某个字符是否是运算符
 98      * 
 99      * @param c
100      * @return 是运算符返回 true,否则返回 false
101      */
102     public static boolean isOperator(char c) {
103         return getInstance(c) != null;
104     }
105 
106     public static boolean isOperator(String str) {
107         return str.length() > 1 ? false : isOperator(str.charAt(0));
108     }
109 
110     /**
111      * 根据运算符获得 Operator 实例
112      * 
113      * @param c
114      * @return 从注册中的 Operator 返回实例,尚未注册返回 null
115      */
116     public static Operator getInstance(char c) {
117         return operators.get(c);
118     }
119 
120     public static Operator getInstance(String str) {
121         return str.length() > 1 ? null : getInstance(str.charAt(0));
122     }
123 
124     /**
125      * 根据操作数进行计算
126      * 
127      * @param left
128      *            左操作数
129      * @param right
130      *            右操作数
131      * @return 计算结果
132      */
133     public abstract BigDecimal eval(BigDecimal left, BigDecimal right);

 

 

 

转载于:https://www.cnblogs.com/lmtoo/archive/2013/04/26/formula.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值