java 带括号 四则运算 计算器

目录

  • 算法
    • 不带括号公式的算法
    • 带括号公式的算法
    • 公式校验
      • 括号对校验
      • 相邻符号校验
      • 占位符数量校验
  • 测试
  • 代码

算法

不带括号公式的算法

  • 定义两个栈:操作栈存+ - * /符号,数值栈存实际的数值
  • 遍历公式字符,遇到符号直接入操作数栈
  • 遇到数值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)));
    }

java 带括号 四则运算 计算器_第1张图片

代码

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

你可能感兴趣的:(#,数据结构与算法,java,开发语言,算法)