国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 數據庫 > MySQL > 正文

使用java處理字符串公式運算的方法

2024-07-24 12:42:10
字體:
來源:轉載
供稿:網友

  在改進一個關于合同的項目時,有個需求,就是由于合同中非數據項的計算公式會根據年份而進行變更,而之前是將公式硬編碼到系統中的,只要時間一變,系統就沒法使用了,因此要求合同中各個非基礎數據的項都能自定義公式,根據設置的公式來自動生成報表和合同中的數據。

  顯然定義的公式都是以字符串來存儲到數據庫的,可是java中沒有這種執行字符串公式的工具或者類,而且是公式可以嵌套一個中間公式。比如:基礎數據dddd是56,而一個公式是依賴dddd的,eeee=dddd*20,而最終的公式可能是這樣:eeee*-12+13-dddd+24。可知eeee是一個中間公式,所以一個公式的計算需要知道中間公式和基礎數據。

這好像可以使用一個解釋器模式來解決,但是我沒有成功,因為括號的優先級是一個棘手的問題,后來又想到可以使用freemarker類似的模板引擎或者java6之后提供的ScriptEngine 腳本引擎,做了個實驗,腳本引擎可以解決,但是這限制了必須使用java6及以上的版本。最終功夫不負有心人,終于找到了完美解決方案,即后綴表達式。我們平時寫的公式稱作中綴表達式,計算機處理起來比較困難,所以需要先將中綴表達式轉換成計算機處理起來比較容易的后綴表達式。

將中綴表達式轉換為后綴表達式具體算法規則:見后綴表達式


a.若為 '(',入棧;

b.若為 ')',則依次把棧中的的運算符加入后綴表達式中,直到出現'(',從棧中刪除'(' ;

c.若為 除括號外的其他運算符 ,當其優先級高于棧頂運算符時,直接入棧。否則從棧頂開始,依次彈出比當前處理的運算符優先級高和優先級相等的運算符,直到一個比它優先級低的或者遇到了一個左括號為止。

?當掃描的中綴表達式結束時,棧中的的所有運算符出棧; 

我們提出的要求設想是這樣的:

復制代碼 代碼如下:m.survivalescaperooms.com

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方法會將所有的中間變量都替換成基礎數據,就是一個遞歸的做法

復制代碼 代碼如下:m.survivalescaperooms.com

public class FormulaParser {
/**
* 匹配變量占位符的正則表達式
*/
private static Pattern pattern = Pattern.compile("http://#//{(.+?)//}");

/**
* 解析公式,并執行公式計算
*
* @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計算后綴表達式

復制代碼 代碼如下:m.survivalescaperooms.com

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("http://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;
}

最后把操作符類貼上
復制代碼 代碼如下:m.survivalescaperooms.com

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);
}
};

/**
* 主站蜘蛛池模板: 民权县| 嘉义市| 通化县| 宜良县| 招远市| 哈尔滨市| 桑植县| 鄯善县| 辽源市| 大石桥市| 阜阳市| 会理县| 黄陵县| 辽中县| 通江县| 屏山县| 洛川县| 雷波县| 阜宁县| 栾川县| 澎湖县| 台东县| 梨树县| 曲松县| 农安县| 临城县| 晋城| 宜丰县| 鹤峰县| 安国市| 台南市| 蒲城县| 金溪县| 镇雄县| 延寿县| 福泉市| 武夷山市| 蓬溪县| 十堰市| 建宁县| 寿宁县|