近期接到一个实现公式解析计算、能够多层嵌套并自动拆解的需求。即对于给定的字符串公式进行解析及计算,实现从持久层自动装载公式,拆解多层嵌套的公式,得到计算结果。大致的设计思路为:定义一个计算器基类,实现基本属性的装载及加减乘除公式的定义,实现公式拆解、运算符分级及解析计算;定义子类实现不同的装载逻辑和公式解析逻辑。
基类:
package cn.ac.sict.calculator;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class Calculator {
//公式id
private String id;
//公式名称
private String name;
//公式内容
private String formula;
//公式类型,true代表formula为数值,false代表formula为计算公式
private boolean type = false;
//计算精度
public static int precision = 0;
//默认值
public static String defaultValue = "0";
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFormula() {
return formula;
}
public void setFormula(String formula) {
//把公式中的负数都转化为0-N的形式,方便计算
if(isValidString(formula)) {
StringBuffer stringBuffer = new StringBuffer();
//匹配公式开头的负数部分或中间的负数部分,即以-开头或-前为其他运算符号
Matcher matcher = Pattern.compile("(^-\d+\.?\d*)|([\(\+\-\*\/]{1}-\d+\.?\d*)").matcher(formula);
int start = 0;
while(matcher.find()) {
String group = matcher.group();
String replacement;
if(group.charAt(1) >= '0' && group.charAt(1) <= '9') {
replacement = "(0" + group + ")";
} else {
replacement = group.substring(0, 1) + "(0" + group.substring(1) + ")";
}
stringBuffer.append(formula, start, matcher.start()).append(replacement);
start = matcher.end();
}
stringBuffer.append(formula, start, formula.length());
this.formula = stringBuffer.toString();
} else {
this.formula = defaultValue;
}
}
public boolean isType() {
return type;
}
public void setType(boolean type) {
this.type = type;
}
protected Calculator() {
}
private static boolean isValidString(String string) {
return string != null && !"".equals(string) && !"null".equalsIgnoreCase(string);
}
private static boolean isValidDigit(String value) {
if(isValidString(value)) {
value = value.trim();
return Pattern.compile("^(\+|-)?\d+($|\.\d+$)").matcher(value).matches();
} else {
return false;
}
}
private static String format(String value) {
StringBuffer expression = new StringBuffer("0");
if(precision > 0) {
expression.append(".");
for(int i=0;i splitFormula() {
int length = formula.length();
List result = new ArrayList<>(length);
StringBuffer item = new StringBuffer();
for(int i=0;i 0) {
result.add(item.toString());
item.setLength(0);
}
item.append(c);
result.add(item.toString());
item.setLength(0);
} else if((c >= '0' && c <= '9') || c == '#' || c == '.') {
item.append(c);
}
}
if(item.length() > 0) {
result.add(item.toString());
}
return result;
}
public abstract void init();
public abstract List parseFormula();
public static String calculate(List list) {
if(list == null || list.size() == 0) {
return defaultValue;
}
Stack operatorStack = new Stack<>();
Stack numberStack = new Stack<>();
int size = list.size();
for(int i=0;i= 2 && operatorStack.size() >= 1 && getPriority(str) <= getPriority(operatorStack.peek())) {
String operator = operatorStack.pop();
String right = numberStack.pop();
String left = numberStack.pop();
numberStack.push(calculate(left, right, operator));
}
operatorStack.push(str);
if(")".equals(str)) {
while(!"(".equals(operatorStack.peek())) {
operatorStack.pop();
}
operatorStack.pop();
}
}
}
while(numberStack.size() >= 2 && operatorStack.size() >= 1) {
String operator = operatorStack.pop();
String right = numberStack.pop();
String left = numberStack.pop();
numberStack.push(calculate(left, right, operator));
}
return numberStack.pop();
}
}
解析json格式数据的子类:
package cn.ac.sict.calculator;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class CalculatorJsonImpl extends Calculator {
public String filePath = "D:\ide\project\Calculator\cn\ac\sict\calculator\data.json";
private CalculatorJsonImpl() {
super();
}
public static CalculatorJsonImpl create(String id) {
CalculatorJsonImpl result = new CalculatorJsonImpl();
result.setId(id);
return result;
}
@Override
public void init() {
JSONArray jsonArray = JSONUtil.readJSONArray(new File(filePath), CharsetUtil.charset("UTF-8"));
jsonArray.stream().forEach(obj -> {
JSONObject jsonObject = JSONUtil.parseObj(obj);
if(getId().equals(jsonObject.getStr("id"))) {
setName(jsonObject.getStr("name"));
setFormula(jsonObject.getStr("formula"));
setType(jsonObject.getBool("type"));
}
});
}
@Override
public List parseFormula() {
List result = new ArrayList<>();
if(isType()) {
result.add(getFormula());
return result;
}
List list = splitFormula();
for(String str : list) {
if(str.startsWith("#")) {
String id = str.substring(1);
CalculatorJsonImpl sub = CalculatorJsonImpl.create(id);
sub.init();
result.addAll(sub.parseFormula());
} else {
result.add(str);
}
}
return result;
}
}
在实际项目中可以把子类定义为从数据库获取公式定义,方便管理。
测试数据:
[
{
"id": "1",
"name": "测试1",
"formula": "1+2",
"type": false
},
{
"id": "2",
"name": "测试2",
"formula": "#1+3",
"type": false
},
{
"id": "3",
"name": "测试3",
"formula": "#2*-1*#4",
"type": false
},
{
"id": "4",
"name": "测试4",
"formula": "5",
"type": true
},
{
"id": "5",
"name": "测试5",
"formula": "2/(3.6+((5-100)+50.3))*(3-1)+6-8",
"type": false
}
]
测试类:
package cn.ac.sict.calculator;
import java.util.List;
public class Test {
public static void main(String[] args) {
Calculator calculator = CalculatorJsonImpl.create("3");
calculator.init();
System.out.println(calculator.getName());
List list = calculator.parseFormula();
System.out.println("解析后的公式为:" + list);
String result = Calculator.calculate(list);
System.out.println("计算结果为:" + result);
calculator = CalculatorJsonImpl.create("5");
calculator.init();
System.out.println(calculator.getName());
list = calculator.parseFormula();
System.out.println("解析后的公式为:" + list);
Calculator.precision = 2;
result = Calculator.calculate(list);
System.out.println("计算结果为:" + result);
}
}
测试结果:
测试3
解析后的公式为:[1, +, 2, +, 3, *, (, 0, -, 1, ), *, 5]
计算结果为:-12测试5
解析后的公式为:[2, /, (, 3.6, +, (, (, 5, -, 100, ), +, 50.3, ), ), *, (, 3, -, 1, ), +, 6, -, 8]
计算结果为:-2.10



