在改進一個關于合同的項目時,有個需求,就是由于合同中非資料項的計算公式會根據年份而進行變更,而之前是将公式寫死到系統中的,隻要時間一變,系統就沒法使用了,是以要求合同中各個非基礎資料的項都能自定義公式,根據設定的公式來自動生成報表和合同中的資料。
顯然定義的公式都是以字元串來存儲到資料庫的,可是java中沒有這種執行字元串公式的工具或者類,而且是公式可以嵌套一個中間公式。比如:基礎資料dddd是56,而一個公式是依賴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