字符串公式计算,包含常用数学公式,java实现

业务需要,做一个用户自定义的公式计算。

公式由前端自定义,生成字符串发给后端,由后端完成计算。

公式需要支持四则运算,支持常用函数,支持函数的嵌套运算。

字符串类似这种

“1-2.5+SUM(3*4,5,SUM(IF(“皮卡”=“皮卡丘”,5,DAYS(“2022-10-19”,“2022-10-29”)),6))”

字符串四则运算,首先下意识就想到后缀表达式,思路就是基于后缀表达式添加函数的兼容。

中缀转后缀之前,首先把字符串分割成列表,这里规定函数中用到字符串为参数,比如日期,字符串,需要用""包起来。

/**
 * 将表达式转为list
 */
private static List<String> expressionToList(String text) {
    int index = 0;
    List<String> list = new ArrayList<>();
    try {
        String opStr = replaceFunctionStr(text);
        do {
            char ch = opStr.charAt(index);
            if (ch < 48 || ch > 57) {
                if (ch == '"') {
                    //是字符参数,截取到下一个引号为止
                    String str = "";
                    index += 1;
                    while (index < opStr.length() && opStr.charAt(index) != '"') {
                        str += opStr.charAt(index);
                        index++;
                    }
                    list.add(str);
                    index++;
                } else {
                    //是操作符,判断多位操作符的情况 例如不等,大于等于
                    if (ch == '>' || ch == '<') {
                        String str = "";
                        while (index < opStr.length() && ((opStr.charAt(index) < 48 || opStr.charAt(index) > 57) && '(' != opStr.charAt(index) && '"' != opStr
                                .charAt(index))) {
                            str += opStr.charAt(index);
                            index++;
                        }
                        list.add(str);
                    } else {
                        //是操作符,直接添加至list中
                        index++;
                        list.add(ch + "");
                    }
                }
            } else if (ch >= 48 && ch <= 57) {
                //是数字,判断多位数的情况
                String str = "";
                while (index < opStr.length() && ((opStr.charAt(index) >= 48 && opStr.charAt(index) <= 57) || '.' == opStr.charAt(index))) {
                    str += opStr.charAt(index);
                    index++;
                }
                list.add(str);
            }
        } while (index < opStr.length());
    } catch (Throwable e) {
        throw new EqException(EqErrorEnum.E_01);
    }
    return list;
}

执行结果:

*[1, -, 2.5, +, S, (, 3, , 4, , 5, , S, (, I, (, 皮卡, =, 皮卡丘, , 5, , D, (, 2022-10-19, , 2022-10-29, ), ), , 6, ), )]

再将中缀表达式List转为后缀表达式List

/**
 * 中缀转后缀
 */
private static List<String> parseToSuffixExpression(List<String> expressionList) {
    Stack<String> opStack = new Stack<>();
    List<String> suffixList = new ArrayList<>();
    for (String item : expressionList) {
        if (isOperator(item)) {
            //是操作符 判断操作符栈是否为空
            if (opStack.isEmpty() || "(".equals(opStack.peek()) || priority(item) > priority(opStack.peek()) || ",".equals(opStack.peek())) {
                //为空或者栈顶元素为左括号或者当前操作符大于栈顶操作符直接压栈
                opStack.push(item);
            } else if (",".equals(item)) {
                //参数间隔符,说明聚合公式还没结束
                if (isOperator(opStack.peek())) {
                    suffixList.add(opStack.pop());
                    opStack.push(item);
                }
            } else {
                //否则将栈中元素出栈入队,直到遇到大于当前操作符或者遇到左括号时
                while (!opStack.isEmpty() && !"(".equals(opStack.peek())) {
                    if (priority(item) <= priority(opStack.peek())) {
                        suffixList.add(opStack.pop());
                    } else {
                        break;
                    }
                }
                //当前操作符压栈
                opStack.push(item);
            }
        } else if (isNumber(item)) {
            suffixList.add(item);
        } else if (isString(item)) {
            suffixList.add(item);
        } else if ("(".equals(item)) {
            //是左括号,压栈
            opStack.push(item);
        } else if (")".equals(item)) {
            int opParamNumber = 0;
            //是右括号 ,将栈中元素弹出入队,直到遇到左括号,左括号出栈,但不入队
            while (!opStack.isEmpty()) {
                if ("(".equals(opStack.peek())) {
                    opStack.pop();
                    if ("S".equals(opStack.peek())) {
                        //左括号的下一个是S,那么S加上参数个数,出栈 入队
                        String op = opStack.pop();
                        opParamNumber += 1;
                        suffixList.add(op + opParamNumber);
                    }
                    if ("D".equals(opStack.peek())) {
                        //左括号的下一个是D,出栈 入队
                        suffixList.add(opStack.pop());
                    }
                    break;
                } else {
                    String pop = opStack.pop();
                    if (",".equals(pop)) {
                        opParamNumber++;
                    } else {
                        suffixList.add(pop);
                    }
                }
            }
        } else {
            throw new RuntimeException("有非法字符!");
        }
    }
    //循环完毕,如果操作符栈中元素不为空,将栈中元素出栈入队
    while (!opStack.isEmpty()) {
        suffixList.add(opStack.pop());
    }
    return suffixList;
}

执行结果:

中缀:

[1, -, 2.5, +, S, (, 3, *, 4, , 5, , S, (, I, (, 皮卡, =, 皮卡丘, , 5, , D, (, 2022-10-19, , 2022-10-29, ), ), , 6, ), )]

后缀:

[1, 2.5, -, 3, 4, *, 5, 皮卡, 皮卡丘, =, 5, 2022-10-19, 2022-10-29, D, I, 6, S2, S3, +]

这里把SUM函数的参数个数拼在操作符后面了,方便后续计算

最后计算

后缀表达式列表循环,如果是数字,入栈,如果是操作符,弹栈,计算

/**
 * 计算
 */
private static Object calculate(List<String> suffixList) {
    Stack<Object> stack = new Stack<>();
    //遍历ls
    for (String item : suffixList) {
        if ((isNumber(item) || isString(item)) && !isOperator(item)) {
            //如果是参数(数字、字符串)
            //入栈
            stack.push(item);
        } else if (isSimpleOp(item)) {
            //加减乘除
            stack.push(doSimple(stack, item));
        } else if (isFlagOp(item)) {
            //等式运算
            stack.push(doJudge(stack, item));
        } else if (item.startsWith("S")) {
            //sum
            BigDecimal result = doSum(stack, item);
            stack.push(result);
        } else if (item.startsWith("I")) {
            //if
            String falseRes = stack.pop().toString();
            String trueRes = stack.pop().toString();
            String flag = stack.pop().toString();
            stack.push(doIf(flag, trueRes, falseRes));
        } else if (item.startsWith("D")) {
            //days
            Integer days = doDays(stack);
            stack.push(days);
        }
    }
    //将最后的stack中的数弹出
    return stack.pop();
}

执行

public static void main(String[] args) {

    String expression = "1-2.5+SUM(3*4,5,SUM(IF(\"皮卡\"=\"皮卡丘\",5,DAYS(\"2022-10-19\",\"2022-10-29\")),6))";
    List<String> expressionList = expressionToList(expression);

    System.out.println("中缀:" + expressionList);
    List<String> suffixList = parseToSuffixExpression(expressionList);

    System.out.println("后缀:" + suffixList);

    System.out.println("结果:" + calculate(suffixList));
}

执行结果:

中缀:[1, -, 2.5, +, S, (, 3, *, 4, , 5, , S, (, I, (, 皮卡, =, 皮卡丘, , 5, , D, (, 2022-10-19, , 2022-10-29, ), ), , 6, ), )]
后缀:[1, 2.5, -, 3, 4, *, 5, 皮卡, 皮卡丘, =, 5, 2022-10-19, 2022-10-29, D, I, 6, S2, S3, +]
结果:31.5

这个实现只能说勉强能用,可维护性比较差,如果要添加新的规则比较复杂的函数,需要去上面改改逻辑,想办法兼容。

后面可能会想别的办法优化。

这里是 可拓展自定义函数的字符串公式计算优化方案

下面是上面三个方法中用到的一些非主要逻辑代码

具体计算

/**
 * 计算天数函数
 */
private static Integer doDays(Stack<Object> stack) {
    long result;
    try {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate endDay = LocalDate.parse(stack.pop().toString(), formatter);
        LocalDate startDay = LocalDate.parse(stack.pop().toString(), formatter);
        result = startDay.until(endDay, ChronoUnit.DAYS);
    } catch (Throwable e) {
        throw new EqException(EqErrorEnum.E_06);
    }

    return Integer.parseInt(Long.toString(result));
}

/**
 * 加减乘除运算
 */
private static Object doSimple(Stack<Object> stack, String op) {
    //pop出两个数,并运算
    BigDecimal num2 = BigDecimal.ZERO;
    BigDecimal num1 = BigDecimal.ZERO;
    try {
        num2 = new BigDecimal(stack.pop().toString());
        num1 = new BigDecimal(stack.pop().toString());
    } catch (Throwable e) {
        throw new EqException(EqErrorEnum.E_02);
    }
    BigDecimal res = BigDecimal.ZERO;
    if (op.equals("+")) {
        res = num1.add(num2);
    } else if (op.equals("-")) {
        res = num1.subtract(num2);
    } else if (op.equals("*")) {
        res = num1.multiply(num2);
    } else if (op.equals("/")) {
        if (num2.compareTo(BigDecimal.ZERO) == 0) {
            throw new EqException(EqErrorEnum.E_03);
        }
        res = num1.divide(num2);
    } else {
        throw new EqException(EqErrorEnum.E_04);
    }
    return res;
}

/**
 * SUM函数
 */
private static BigDecimal doSum(Stack<Object> stack, String op) {
    int paramNum = Integer.parseInt(op.substring(1));
    BigDecimal result = BigDecimal.ZERO;
    for (int i = 0; i < paramNum; i++) {
        try {
            BigDecimal param = new BigDecimal(stack.pop().toString());
            result = result.add(param);
        } catch (Throwable e) {
            throw new EqException(EqErrorEnum.E_06);
        }
    }
    return result;
}

/**
 * if函数
 */
private static String doIf(String flag, String trueRes, String falseRes) {
    Boolean aBoolean;
    try {
        aBoolean = Boolean.valueOf(flag);
    } catch (Throwable e) {
        throw new EqException(EqErrorEnum.E_06);
    }
    return aBoolean ? trueRes : falseRes;
}

/**
 * 不等式
 */
private static Boolean doJudge(Stack<Object> stack, String item) {
    Object num2 = stack.pop();
    Object num1 = stack.pop();
    boolean res = false;
    if (item.equals("=")) {
        res = num1.equals(num2);
    } else if (item.equals(">")) {
        try {
            BigDecimal param2 = new BigDecimal(num2.toString());
            BigDecimal param1 = new BigDecimal(num1.toString());
            res = param1.compareTo(param2) > 0;
        } catch (Throwable e) {
            throw new EqException(EqErrorEnum.E_02);
        }
    } else if (item.equals("<")) {
        try {
            BigDecimal param2 = new BigDecimal(num2.toString());
            BigDecimal param1 = new BigDecimal(num1.toString());
            res = param1.compareTo(param2) < 0;
        } catch (Throwable e) {
            throw new EqException(EqErrorEnum.E_02);
        }
    } else if (item.equals(">=")) {
        try {
            BigDecimal param2 = new BigDecimal(num2.toString());
            BigDecimal param1 = new BigDecimal(num1.toString());
            res = param1.compareTo(param2) >= 0;
        } catch (Throwable e) {
            throw new EqException(EqErrorEnum.E_02);
        }
    } else if (item.equals("<=")) {
        try {
            BigDecimal param2 = new BigDecimal(num2.toString());
            BigDecimal param1 = new BigDecimal(num1.toString());
            res = param1.compareTo(param2) < 0;
        } catch (Throwable e) {
            throw new EqException(EqErrorEnum.E_02);
        }
    } else if (item.equals("<>")) {
        res = !num1.equals(num2);
    } else {
        throw new EqException(EqErrorEnum.E_04);
    }
    return res;
}

一些判断

private static String replaceFunctionStr(String text) {
    return text.replaceAll("SUM", "S").replaceAll("IF", "I").replaceAll("DAYS", "D");
}
 /**
     * 判断字符串是否为操作符
     */
    public static boolean isOperator(String op) {
        return isSimpleOp(op) || isFlagOp(op) || isExpressOp(op) || isOtherOp(op);
    }

    /**
     * 判断字符串是否为加减乘除
     */
    private static boolean isSimpleOp(String op) {
        return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/");
    }

    /**
     * 判断字符串是否为等式操作符
     */
    private static boolean isFlagOp(String op) {
        return op.equals(">") || op.equals("<") || op.equals("=") || op.equals(">=") || op.equals("<=") || op.equals("<>");
    }

    /**
     * 判断字符串是否为公式操作符
     */
    private static boolean isExpressOp(String op) {
        return op.startsWith("S") || op.equals("I") || op.equals("D");
    }

    /**
     * 判断字符串是否为其他操作符
     */
    private static boolean isOtherOp(String op) {
        return op.equals(",");
    }

    /**
     * 判断是否为数字
     */
    public static boolean isNumber(String num) {
        return num.matches("\\d+(\\.\\d+)?");
    }

    /**
     * 判断是否为合法字符串参数 允许由汉字 数字 大小写英文字母 和 - 组成的字符串
     */
    private static boolean isString(String num) {
        return num.matches("[\\u4e00-\\u9fa50-9a-zA-Z-]{1,}$");
    }

    /**
     * 获取操作符的优先级
     */
    private static int priority(String op) {
        if (op.equals("*") || op.equals("/") || isFlagOp(op) || isExpressOp(op)) {
            return 1;
        } else if (op.equals("+") || op.equals("-")) {
            return 0;
        }
        return -1;
    }

报错枚举

public enum  EqErrorEnum implements IErrorCode {
    E_01(30001L, "公式解析异常!"),
    E_02(30002L, "四则运算参数异常!"),
    E_03(30003L, "分母不能为0!"),
    E_04(30004L, "运算符有误!"),
    E_05(30005L, "等式运算参数异常!"),
    E_06(30006L, "函数参数异常!"),

    ;
    private Long code;
    private String message;

    EqErrorEnum(Long code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public long getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java可以通过动态计算公式实现数学计算的功能。在Java中,可以使用脚本引擎来实现动态计算公式的功能,常用的脚本引擎包括JavaScript引擎、Groovy引擎等。 首先,需要导入相应的脚本引擎库,例如,对于JavaScript引擎,可以导入javax.script包。 接下来,可以使用以下代码进行动态计算公式: ```java import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class DynamicFormulaCalculation { public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("javascript"); try { // 定义需要计算公式 String formula = "2 * (3 + 4)"; // 执行公式计算 Object result = engine.eval(formula); System.out.println("计算结果:" + result); } catch (ScriptException e) { e.printStackTrace(); } } } ``` 以上代码中,首先通过ScriptEngineManager获取一个脚本引擎管理器,然后使用getEngineByName("javascript")获取JavaScript引擎实例。 然后,定义需要计算公式,可以使用字符串形式表示。在本例中,公式为2 * (3 + 4)。然后,调用engine.eval()方法执行公式计算,得到计算结果。 最后,将计算结果打印输出。 通过这种方式,我们可以实现动态计算公式的功能,具体的公式可以根据实际需求来定义和改变,从而满足不同的数学计算需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LxyrichBos

老板~坐下喝杯茶啊~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值