算法
不带括号公式的算法
- 定义两个栈:操作栈存+ - * /符号,数值栈存实际的数值
- 遍历公式字符,遇到符号直接入操作数栈
- 遇到数值a
- 如果数值栈空则a直接进入数值栈
- 数值栈不为空则弹出 操作栈 的栈顶(从栈中删除)
- 栈顶为 + ,则将符号重新入栈,数值进入数值栈。
- 栈顶为 - ,则将符号变为 + 并入栈, 数值a取反并入数值栈
- 栈顶为 * / ,则弹出 数值栈 栈顶数值b,将b与a用符号进行计算,将结果放入数值栈
- 遍历结束,操作栈中只有 + 符号,数值栈最少有一个数值。每次弹出一个符号和两个数值,计算后结果入数值栈,直到数值栈只剩下一个元素(操作栈可能剩下一个 +),这个元素就是最终的结果。
带括号公式的算法
递归思想,每个括号对都可以 用 不带括号公式的算法将括号里的结果算出来。
公式校验
括号对校验
(()()()(())) 类似这种成对括号出现才是合法的公式。
- 遇到左括号入栈,
- 遇到右括号判断栈中是否有元素
- 有元素则弹出栈顶
- 无元素则不合法(多余的右括号)
- 遍历完后判断栈中是否有元素,有元素则不合法(多余的左括号)
相邻符号校验
- 四则符号后不能跟着右括号
- 四则符号不能相邻
- 占位符不能相邻(默认占位符为四则运算、括号、空字符以外的任何字符)
占位符数量校验
占位符数量应当与数值参数数量相同
测试
public static void main(String[] args) {
NormalCalculator normalCalculator = new NormalCalculator();
// (3)*((4-5+6)/7)
System.out.println(normalCalculator.calculate("(a)*((a-b+c)/d)", Arrays.asList(3d, 4d, 5d, 6d, 7d)));
}
代码
CalculateResult
@Data
public class CalculateResult {
Double result;
List<String> calculateLog;
String formula;
List<Double> paramList;
public CalculateResult() {
calculateLog = new ArrayList<>();
}
public void addLog(String log){
calculateLog.add(log);
}
}
AbstractCalculate
@Slf4j
public abstract class AbstractCalculate {
protected final CalculateResult calculateResult = new CalculateResult();
/**
* 外部调用计算结果
* @param formula 公式
* @param paramList 参数列表
* @return 结果
*/
public CalculateResult calculate(String formula, List<Double> paramList) {
log.info("start->formula:" + formula + "paramList:" + paramList);
formula = formatFormula(formula);
check(formula,paramList);
calculateLogic(formula, paramList);
CalculateResult calculateResult = getCalculateResult();
reset();
log.info("end");
return calculateResult;
}
/**
* 计算完后重置信息,可重复利用对象
*/
protected abstract void reset();
/**
* 格式化公式
* @param formula 公式
* @return 格式化公式
*/
protected abstract String formatFormula(String formula);
/**
* 校验公式是否合法
* @param formula 参数
* @param paramList 参数列表
*/
protected abstract void check(String formula,List<Double> paramList);
/**
* 核心计算逻辑
* @param formula 参数
* @param paramList 参数列表
* @return
*/
protected abstract Double calculateLogic(String formula, List<Double> paramList);
/**
* 获取计算结果
* @return 结果
*/
protected abstract CalculateResult getCalculateResult();
}
NormalCalculator
@Slf4j
public class NormalCalculator extends AbstractCalculate {
/**
* 计算,直到遇到左括号
*/
private final static String CALCULATE_ALL = "calculateAll";
/**
* 计算两个数
*/
private final static String CALCULATE_TWO = "calculateTwo";
/**
* 计算全部
*/
private final static String WAIT_CALCULATE = "waitCalculate";
@Getter
enum Operator {
RIGHT_BRACKET(')', CALCULATE_ALL, null),
LEFT_BRACKET('(', WAIT_CALCULATE, null),
ADD('+', WAIT_CALCULATE, Double::sum),
SUBTRACT('-', WAIT_CALCULATE, (a, b) -> a - b),
MULTIPLY('*', CALCULATE_TWO, (a, b) -> a * b),
DIVIDE('/', CALCULATE_TWO, (a, b) -> a / b);
private final char symbol;
private final String type;
private final BiFunction<Double, Double, Double> function;
Operator(char symbol, String type, BiFunction<Double, Double, Double> function) {
this.symbol = symbol;
this.type = type;
this.function = function;
}
private final static Map<Character, Operator> SYMBOL_MAP =
Arrays.stream(Operator.values()).collect(Collectors.toMap(Operator::getSymbol, v -> v));
/**
* 根据符号取信息类型
*
* @param symbol 符号
* @return 类型
*/
public static Operator getOperatorBySymbol(char symbol) {
Operator operator = SYMBOL_MAP.get(symbol);
if (operator == null) {
throw new IllegalArgumentException("不是符号!" + symbol);
}
return operator;
}
/**
* 是否是四则运算
*
* @param symbol 符号
* @return 是否
*/
public static boolean isFourOperator(char symbol) {
Operator operator = SYMBOL_MAP.get(symbol);
return ADD.equals(operator) || SUBTRACT.equals(operator) || DIVIDE.equals(operator) || MULTIPLY.equals(operator);
}
/**
* 判断是否是括号
*
* @param symbol 符号
* @return 是否
*/
public static boolean isBracket(char symbol) {
Operator operator = SYMBOL_MAP.get(symbol);
return LEFT_BRACKET.equals(operator) || RIGHT_BRACKET.equals(operator);
}
/**
* 判断是否为符号
*
* @param symbol 符号
* @return 是否
*/
public static boolean isSymbol(char symbol) {
return SYMBOL_MAP.containsKey(symbol);
}
}
private int paramIndex = 0;
private int startIndex = 0;
private final Stack<Double> numStack = new Stack<>();
private final Stack<Operator> operatorStack = new Stack<>();
@Override
protected void reset() {
paramIndex = 0;
startIndex = 0;
numStack.clear();
operatorStack.clear();
}
@Override
protected String formatFormula(String formula) {
if (StringUtils.isEmpty(formula)) {
return "a";
}
return String.join("", formula.split("\\s+"));
}
@Override
protected void check(String formula, List<Double> paramList) {
checkBracket(formula);
checkParamNum(formula, paramList);
checkNeighbor(formula);
}
@Override
protected Double calculateLogic(String formula, List<Double> paramList) {
while (startIndex < formula.length()) {
char symbol = formula.charAt(startIndex++);
// 左括号则入操作栈,计算左括号到右括号之间的公式,结果进入数值栈
if (symbol == Operator.LEFT_BRACKET.getSymbol()) {
operatorStack.add(Operator.LEFT_BRACKET);
numStack.add(calculateLogic(formula, paramList));
// 如果是右括号,则将括号之间的公式计算。
} else if (symbol == Operator.RIGHT_BRACKET.getSymbol()) {
while (!Operator.LEFT_BRACKET.equals(operatorStack.peek())) {
calculateTwo(operatorStack.pop());
}
operatorStack.pop();
return numStack.pop();
// 如果是算子+-*/,则算子入操作栈
} else if (Operator.isSymbol(symbol)) {
Operator operator = Operator.getOperatorBySymbol(symbol);
operatorStack.add(operator);
// 如果是占位符,会计算或者不计算。但最终两个括号之间只会剩下加法的算子。
} else {
Operator operator;
// 如果操作栈为空 或者 操作栈栈顶是左括号,则直接将参数值放到 数值栈
if (operatorStack.isEmpty() || operatorStack.peek().equals(Operator.LEFT_BRACKET)) {
numStack.add(paramList.get(paramIndex++));
continue;
} else {
// 操作栈不为空 并且 栈顶不是左括号,遇到栈顶为-的算子,则将算子替换为+,并将数值取反,加入数值栈
operator = operatorStack.pop();
if (operator.equals(Operator.SUBTRACT)) {
operator = Operator.ADD;
numStack.add(-paramList.get(paramIndex++));
} else {
numStack.add(paramList.get(paramIndex++));
}
}
// 获取操作栈栈顶算子类型为需要立即计算的"*/",则直接计算结果。
// 计算结果的方式为 一个算子出栈,两个数值出栈,计算结果,结果进入数值栈
String type = operator.getType();
if (CALCULATE_TWO.equals(type)) {
calculateTwo(operator);
// 算子类型为不需要立即计算的"+-(",则不计算,算子重新进入操作栈
} else if (WAIT_CALCULATE.equals(type)) {
operatorStack.add(operator);
} else {
throw new IllegalStateException("不正确的类型,代码有误:" + type);
}
}
}
while (numStack.size() != 1) {
calculateTwo(operatorStack.pop());
}
Double result = numStack.pop();
calculateResult.setResult(result);
calculateResult.setFormula(formula);
calculateResult.setParamList(paramList);
return result;
}
@Override
protected CalculateResult getCalculateResult() {
return calculateResult;
}
private void checkNeighbor(String formula) {
for (int i = 0; i < formula.length(); i++) {
char symbol = formula.charAt(i);
if (i != formula.length() - 1) {
char nextSymbol = formula.charAt(i + 1);
if (Operator.isFourOperator(symbol) && Operator.isFourOperator(nextSymbol)) {
throw new IllegalArgumentException(formula + ":" + symbol + "不能在" + nextSymbol + "前面");
}
if (Operator.isFourOperator(symbol) && Operator.RIGHT_BRACKET.getSymbol() == nextSymbol) {
throw new IllegalArgumentException(formula + ":" + symbol + "不能与)相连");
}
if (!Operator.isSymbol(symbol) && !Operator.isSymbol(nextSymbol)) {
throw new IllegalArgumentException(formula + ":" + symbol + "不能与" + nextSymbol + "相邻");
}
}
if (i == formula.length() - 1 && Operator.isFourOperator(symbol)) {
throw new IllegalArgumentException(formula + ":" + symbol + "不能在结尾");
}
}
}
/**
* 检测括号是否成对出现,并且左括号在右括号左边。
*
* @param formula 规则
*/
private void checkBracket(String formula) {
Stack<Character> bracketStack = new Stack<>();
for (int i = 0; i < formula.length(); i++) {
if (Operator.LEFT_BRACKET.getSymbol() == formula.charAt(i)) {
bracketStack.add(Operator.LEFT_BRACKET.getSymbol());
} else if (Operator.RIGHT_BRACKET.getSymbol() == formula.charAt(i)) {
if (bracketStack.isEmpty()) {
throw new IllegalArgumentException("右括号格式不正确!" + formula);
} else {
bracketStack.pop();
}
}
}
if (!bracketStack.isEmpty()) {
throw new IllegalArgumentException("多余的左括号(!" + formula);
}
}
private void checkParamNum(String formula, List<Double> paramList) {
if (CollectionUtils.isEmpty(paramList)) {
throw new IllegalArgumentException("参数列表paramList为空");
}
int paramNum = paramList.size();
for (int i = 0; i < formula.length(); i++) {
if (!Operator.isSymbol(formula.charAt(i))) {
paramNum--;
}
}
if (paramNum != 0) {
throw new IllegalArgumentException("公式占位符数量与参数类表不同!formula:" + formula + ";paramList:" + paramList);
}
}
private void calculateTwo(Operator operator) {
char symbol = operator.getSymbol();
if (symbol == Operator.LEFT_BRACKET.getSymbol() || symbol == Operator.RIGHT_BRACKET.getSymbol()) {
return;
}
Double second = numStack.pop();
Double first = numStack.pop();
Double result = operator.getFunction().apply(first, second);
numStack.add(result);
String logInfo = CALCULATE_TWO + ":" + first + operator.getSymbol() + second + "=" + result;
log.info(logInfo);
calculateResult.addLog(logInfo);
}
}