【Java 字符串数学运算公式(夹带次方和括号)的计算】

字符串数学运算公式的计算

数学运算公式就不必多介绍了,简单交流一下Java怎么实现数学运算公式的计算,在看到此篇博客前,如果你有需求,可能已经翻阅了无数篇文章了,或许已经得到你想要的答案,不过说不定也还没有找到你想要的,这里分享一下本人在开发中的求与获。

字符串运算公式

最简单的情况: 3 + 4 3 + 4 3+4
次简单的情况: ( 3 + 4 ) ∗ 5 (3 + 4) * 5 (3+4)5
稍有难度: 3 2 + 4 3^2 + 4 32+4
疑难: ( 1 / 2 3 + ( 3 − 4.1 ) 3 − 1 ) 3 ∗ 888 / 33 (1 / 2^3 + (3 - 4.1)^3 - 1 )^3 * 888 / 33 (1/23+(34.1)31)3888/33
针对以上几种情况,一二种情况就不说了,对于后面几种,夹带次方和嵌套括号运算的,踩坑之路且漫漫!

分析

第一种情况: 不说了!
第二种情况: 也不说了!
第三种情况: 3的二次方需要替换成3 * 3,如果是3的三次放则替换成3 * 3 * 3,咱不用Math.pow(),因为底数和指数不是固定的。
第四种情况: 看见这种这样复杂的运算公式,想想就头疼,思绪万千,先计算括号里面的值,如果遇到次方,将次方转为乘除的样式去计算,算式或许将会变得简单。比如将疑难公式转为先解析成1/2/2/2 + (-1.1)(-1.1)(-1.1) - 1,把这个值计算完,乘方后,再乘以888除以33。当然,第四种情况,既然是字符串,次方可能是⁰,¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,不过这简单,替换成^就可以了。
1.先将字符串的次方替换成我们熟悉的^代表次方的方式,然后以^所在字符索引往前移动,当遇到运算符或者到字符串起始位置之间的字符即为底数;往后移动,当遇到运算符或者到字符串末尾之间的字符即为指数,然后再换成乘数或者除数的运算形式,这里为什么一再强调乘数或者除数,因为如果指数幂做分母,必须要换成除法的形式,比如1/2^3即需要根据"/"进行判断,然后将其换成1/2/2/2。
2.括号多层嵌套,需要先将内层括号的运算表达式识别出来,计算完成后再计算外层括号的表达式。怎么从内层括号一层一层的剥,可以借助栈,先将字符都往栈里面放,当遇到)时,往栈里面取字符,直到取到(,然后把取出来的字符串【简单的运算表达式】计算完成后,将原计算完后的值重新放入栈中,这样,既完成了括号内表达式的计算,又将括号移除了。
3.表达式的值怎么计算?要自己重新写或学习的在网上找一下都有,更多的也是利用栈完成计算。不过我比较懒,走了一条投机的路,引用了ScriptEngine类的eval()方法来计算运算表达式。

实现代码

package com.pks.common.utils;

import org.apache.commons.lang3.StringUtils;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * @description: 运算公式计算工具类
 * @author: pks
 */
public class CalculateUtils {
    public static final List<String> OPERATORS = Arrays.asList("+", "-", "*", "/", "(", ")");
	/**
     * 运算公式
     */
    public static final String CALCULATION_FORMULA = "^(?:[\\*\\/\\+\\-\\s]*\\d+(?:\\.\\d*)?){1,}$";
    
	/**
     * 次方映射 ⁰,¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹
     * 百分号%用 /100替换 乘以符号的替换 减号的替换
     */
    public static final Map<String, String> SPECIAL_POWER_MMAP = new ImmutableMap.Builder<String, String>()
        .put("⁰", "^0")
        .put("¹", "^1")
        .put("²", "^2")
        .put("³", "^3")
        .put("⁴", "^4")
        .put("⁵", "^5")
        .put("⁶", "^6")
        .put("⁷", "^7")
        .put("⁸", "^8")
        .put("⁹", "^9")
        .put("×", "*")
        .put("x", "*")
        .put("X", "*")
        .put("一", "-")
        .put("+", "+")
        .put("÷", "/")
        .put("%", "/100")
        .build();

    /**
     * 计算公式的值
     *
     * @param formula 计算公式
     * @return 计算的值
     */
    public static String calculateFormula(String formula) {
        formula = transformCalculateFormula(formula);
        formula = getCalculateFormula(formula);
        if (!isCalculateFormula(formula)) {
            return formula;
        }
        // 使用script引擎来计算
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        try {
            Object result = engine.eval(formula);
            BigDecimal bd = new BigDecimal(result.toString());
            // 默认保留两位小数,移除多余的0,转成非科学计数法的字符串
            return bd.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
        } catch (ScriptException e) {
            return formula;
        }
    }

    /**
     * 转换计算公式
     *
     * @param formula 公式
     * @return 计算公式
     */
    public static String transformCalculateFormula(String formula) {
        // 如果次方是以上标的形式写的,需要替换成^表示次方; %用/100表示;× 用 * 表示
        Set<String> powerStrSet = SPECIAL_POWER_MMAP.keySet();
        for (String power : powerStrSet) {
            if (formula.contains(power)) {
                formula = formula.replaceAll(power, SPECIAL_POWER_MMAP.get(power));
            }
        }

        // 替换空格
        formula = formula.replaceAll(" ", "");
        return formula;
    }

    /**
     * 判断字符串是否是数学计算公式
     *
     * @param formula 字符串公式
     * @return true or false
     */
    public static boolean isCalculateFormula(String formula) {
        if (StringUtils.isEmpty(formula)) {
            return false;
        }
        Pattern pattern = Pattern.compile(CALCULATION_FORMULA);
        return pattern.matcher(formula).matches();
    }

    /**
     * 获取次方替换的字符串 除了除号,其余均替换成乘号
     * (10^2替换成(10*10
     * +10^2替换成+10*10
     * -10^2替换成-10*10
     * *10^2替换成*10*10
     * /10^2替换成/10/10
     *
     * @return 替换结果
     */
    public static String getCalculateFormula(String formula) {
        // 添加一个!作为结束替换的标识
        String value = StringUtils.join(formula, "!");
        char[] charArray = value.toCharArray();
        StringBuilder sb = new StringBuilder();
        // 记录前一个运算符的位置,将其值加载替换值的最前面,用来判断次方用乘法还是除法
        int preIndex = 0;
        for (int i = 0 ; i < charArray.length; i++) {
            if (charArray[i] == '+' || charArray[i] == '-' || charArray[i] == '*' || charArray[i] == '/'
                || charArray[i] == '(' || charArray[i] == ')' || charArray[i] == '!') {
                if (sb.toString().contains("^")) {
                    int currentIndex = preIndex;
                    StringBuilder preStr = new StringBuilder();
                    // 如果乘方前面的符号是小数点要继续往前移动,把小数取到
                    while (currentIndex >= 0 && !OPERATORS.contains(Character.toString(charArray[currentIndex]))) {
                        preStr.insert(0, charArray[currentIndex]);
                        currentIndex --;
                    }
                    currentIndex = Math.max(currentIndex, 0);
                    // 乘方前面的计算符号
                    if (OPERATORS.contains(Character.toString(charArray[currentIndex]))) {
                        preStr.insert(0, charArray[currentIndex]);
                    } else {
                        preStr = new StringBuilder();
                    }
                    String power = preStr + sb.toString();
                    formula = formula.replace(power, replacePowerStr(power));
                }
                preIndex = i;
                sb = new StringBuilder();
            } else {
                sb.append(charArray[i]);
            }
        }
        return formula;
    }

    /**
     * 替换幂
     *
     * @param replace 需要替换的字符串
     * @return 替换结果
     */
    public static String replacePowerStr(String replace) {
        if (StringUtils.isEmpty(replace) || !replace.contains("^")) {
            return replace;
        }
        String result;
        String[] powers = replace.split("\\^");
        if (powers.length != 2) {
            return replace;
        }
        // 第一个字符串带指数,且第一个字符串的第一个字符是运算符
        String computerStr = powers[0];
        String operateStr = powers[0].substring(0, 1);
        String baseNumber = powers[0].substring(1);
        // 指数幂在字符串开头
        if (!OPERATORS.contains(operateStr)) {
            operateStr = "";
            baseNumber = computerStr;
        }
        // 第二个字符串是幂
        String power = powers[1];
        List<String> replaceList = new ArrayList<>();
        for (int i = 0; i < Integer.parseInt(power); i ++) {
            replaceList.add(baseNumber);
        }
        if (replace.contains("/")) {
            result = StringUtils.join(operateStr, String.join("/", replaceList));
        } else {
            result = StringUtils.join(operateStr, String.join("*", replaceList));
        }
        return result;
    }

    /**
     * 计算括号里面的内容(小括号)
     *
     * @param computerStr 需要计算的字符串
     * @return 没有括号的表达式
     */
    public static String removeBracket(String computerStr) {
        if (StringUtils.isEmpty(computerStr) || (!StringUtils.contains(computerStr, "(")
            && !StringUtils.contains(computerStr, ")"))) {
            return computerStr;
        }
        // 将空格移除
        computerStr = computerStr.replaceAll(" ", "");
        Stack<String> originStrStack = new Stack<>();
        Stack<String> computerStack = new Stack<>();
        char[] chars = computerStr.toCharArray();
        for (char c : chars) {
            if (c != ')') {
                originStrStack.push(Character.toString(c));
            } else {
                boolean computerFlag = true;
                while (computerFlag) {
                    String originChar = originStrStack.pop();
                    computerStack.push(originChar);
                    if (originChar.equals("(")) {
                        computerFlag = false;
                        StringBuilder sb = new StringBuilder();
                        while (!computerStack.isEmpty()) {
                            sb.append(computerStack.pop());
                        }
                        String replaceFormula = sb.toString() + c;  // 需要替换的公式
                        String computerFormula = replaceFormula.replace("(", "").replace(")", "");  // 需要计算的公式,不需要带括号
                        String computerResult = calculateFormula(computerFormula); // 计算括号里面的公式
                        originStrStack.push(computerResult);  // 替换的结果反填给原始栈
                    }
                }
            }
        }
        StringBuilder noBracketFormula = new StringBuilder();
        while (!originStrStack.isEmpty()) {
            noBracketFormula.insert(0, originStrStack.pop());
        }
        return noBracketFormula.toString();
    }

    public static void main(String[] args) {
        String computerStr1 = "3 + 4";
        String computerStr2 = "(3 + 4) * 5";
        String computerStr3 = "3^2 + 4";
        String computerStr4 = "(1 / 2³ + (3 - 4.1)³ - 1 )³  * 888 / 33";
        System.out.println(calculateFormula(removeBracket(computerStr1)));
        System.out.println(calculateFormula(removeBracket(computerStr2)));
        System.out.println(calculateFormula(removeBracket(computerStr3)));
        System.out.println(calculateFormula(removeBracket(computerStr4)));
    }
}

次方的字符串只处理了10次方以内的,如果有更高次的直接写成类如2^32即可。如果实在是有需求要写成2³²,自行写一个方法处理一下转成2^32也不是什么难事。以上代码纯个人编写,保不齐存在漏洞,如若发现,还请指正。

  • 31
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值