在改进一个关于合同的项目时,有个需求,就是由于合同中非数据项的计算公式会根据年份而进行变更,而之前是将公式硬编码到系统中的,只要时间一变,系统就没法使用了,因此要求合同中各个非基础数据的项都能自定义公式,根据设置的公式来自动生成报表和合同中的数据。
显然定义的公式都是以字符串来存储到数据库的,可是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,字符串,公式运算
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]