主頁 > 後端開發 > java 通過決議字串數學運算式簡單進行計算(包括自定義函式以及帶括號的數學運算式)

java 通過決議字串數學運算式簡單進行計算(包括自定義函式以及帶括號的數學運算式)

2021-01-08 07:23:27 後端開發

package com.jxv.common.utils;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

import static com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum.getSelfMathFormulaEnum;
import static com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum.getSelfMathFormulaNames;

/**
* @Author fangzhenxun
* @Description 數學計算公式(精確)
* @Date 2020/12/30 13:30
**/
public class MathCalculatorUtil {

private static final Logger log = LoggerFactory.getLogger(MathCalculatorUtil.class);

/**
* 所支持的運算運算子集合, 兩元運算子
**/
private static final Set<Character> operateSet = new HashSet<>();


/**
* 初始化
**/
static {
//加
operateSet.add('+');
//減
operateSet.add('-');
//乘
operateSet.add('*');
//除
operateSet.add('/');
//求余
operateSet.add('%');
}

/**
* 自定義數學公式列舉
**/
enum SelfMathFormulaEnum {


abs("abs", 1, 3, "abs(x)", "回傳數的絕對值"),
acos("acos", 1, 4,"acos(x)", "回傳數的反余弦值"),
asin("asin", 1, 4,"asin(x)", "回傳數的反正弦值"),
atan("atan", 1, 4,"atan(x)", "以介于 -PI/2 與 PI/2 弧度之間的數值來回傳 x 的反正切值"),
ceil("ceil", 1, 4,"ceil(x)", "對數進行上舍入"),
cos("cos", 1, 3,"cos(x)", "回傳數的余弦"),
exp("exp", 1, 3,"exp(x)", "回傳 e 的指數"),
floor("floor", 1, 5,"floor(x)", "對數進行下舍入"),
log("log", 1, 3,"log(x)", "回傳數的自然對數(底為e)"),
max("max", 2, 3,"max(x,y)", "回傳 x 和 y 中的最高值"),
min("min", 2, 3,"min(x,y)", "回傳 x 和 y 中的最低值"),
pow("pow", 2, 3,"pow(x,y)", "回傳 x 的 y 次冪"),
round("round", 1, 5,"round(x)", "把數四舍五入為最接近的整數"),
sin("sin", 1, 3,"sin(x)", "回傳數的正弦"),
sqrt("sqrt", 1, 4,"sqrt(x)", "回傳數的平方根"),
tan("tan", 1, 3,"tan(x)", "回傳角的正切");


/**
* 公式名稱
**/
private String formulaName;

/**
* 公式引數數量
**/
private Integer formulaArgCount;

/**
* 公式名稱長度
**/
private Integer formulaNameLength;

/**
* 公式運算式
**/
private String formulaExpresion;

/**
* 公式描述
**/
private String description;

/**
* @param formulaName
* @param formulaArgCount
* @return com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum
* @Author fangzhenxun
* @Description 根據自定義公式名稱,和引數數量回傳匹配的列舉物體
* @Date 2020/12/31 10:14
**/
public static SelfMathFormulaEnum getSelfMathFormulaEnum(String formulaName, Integer formulaArgCount) {
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
if (selfMathFormulaEnum.getFormulaName().equals(formulaName) && selfMathFormulaEnum.getFormulaArgCount().equals(formulaArgCount)) {
return selfMathFormulaEnum;
}
}
return null;
}

/**
* @Author fangzhenxun
* @Description 根據名稱獲取函式名
* @Date 2020/12/31 17:10
* @param formulaName
* @return com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum
**/
public static SelfMathFormulaEnum getSelfMathFormulaEnum(String formulaName) {
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
if (selfMathFormulaEnum.getFormulaName().equals(formulaName)) {
return selfMathFormulaEnum;
}
}
return null;
}

/**
* @param
* @return java.util.List<java.lang.String>
* @Author fangzhenxun
* @Description 獲取自定義公式的簡單名稱集合
* @Date 2020/12/31 14:44
**/
public static List<String> getSelfMathFormulaNames() {
List<String> formulaNames = new ArrayList<>();
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
formulaNames.add(selfMathFormulaEnum.getFormulaName());
}
return formulaNames;
}

/**
* @Author fangzhenxun
* @Description 獲取所有的自定義函式列舉
* @Date 2021/1/6 10:27
* @param
* @return java.util.List<com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum>
**/
public static List<SelfMathFormulaEnum> getSelfMathFormulas() {
List<SelfMathFormulaEnum> formulaNames = new ArrayList<>();
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
formulaNames.add(selfMathFormulaEnum);
}
return formulaNames;
}


SelfMathFormulaEnum(String formulaName, Integer formulaArgCount, Integer formulaNameLength, String formulaExpresion, String description) {
this.formulaName = formulaName;
this.formulaArgCount = formulaArgCount;
this.formulaNameLength = formulaNameLength;
this.formulaExpresion = formulaExpresion;
this.description = description;
}

public Integer getFormulaNameLength() {
return formulaNameLength;
}

public void setFormulaNameLength(Integer formulaNameLength) {
this.formulaNameLength = formulaNameLength;
}

public String getFormulaName() {
return formulaName;
}

public void setFormulaName(String formulaName) {
this.formulaName = formulaName;
}

public Integer getFormulaArgCount() {
return formulaArgCount;
}

public void setFormulaArgCount(Integer formulaArgCount) {
this.formulaArgCount = formulaArgCount;
}

public String getFormulaExpresion() {
return formulaExpresion;
}

public void setFormulaExpresion(String formulaExpresion) {
this.formulaExpresion = formulaExpresion;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}


/**
* JavaScript腳本引擎,Java SE 6開始支持
**/
private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");


/**
* @param str
* @return boolean
* @Author fangzhenxun
* @Description 判斷字串是否為數字(浮點型別也包括, 以及正負符號)
* @Date 2020/12/25 18:18
**/
public static boolean isNumber(String str) {
String reg = "^[-\\+]?[0-9]+(.[0-9]+)?$";
return str.matches(reg);
}


/**
* @param mathFormulaScript 數學公式字串,如:mathFormula = (2*3-45/5+(9)+9%5 +2*(1+2) + Math.sqrt(3))/9.0
* 注意:如果使用開根號等三角函式等一些高級計算,則使用Math中字串來替代,如4開根號==>Math.sqrt(4)
* 具體使用查看:https://www.w3school.com.cn/jsref/jsref_obj_math.asp
* @param retainDigit 保留幾位小數
* @return java.lang.String
* @Author fangzhenxun
* @Description 簡單公式計算,會有精度丟失問題
* @Date 2020/12/23 13:13
**/
public static String simpleFormulaScript(String mathFormulaScript, int retainDigit) {
try {
if (StringUtils.isNotEmpty(mathFormulaScript)) {
mathFormulaScript = "(" + mathFormulaScript + ").toFixed(" + retainDigit + ")";
}
return JSON.toJSONString(scriptEngine.eval(mathFormulaScript));
} catch (ScriptException e) {
log.error("非法數學公式!");
e.printStackTrace();
throw new RuntimeException("非法數學公式!");
}
}

/**
* @param mathFormulaScript
* @return java.lang.String
* @Author fangzhenxun
* @Description 多載方法,默認進度小數保留兩位
* @Date 2020/12/23 13:48
**/
public static String simpleFormulaScript(String mathFormulaScript) {
return simpleFormulaScript(mathFormulaScript, 2);
}


/**
* @param arg
* @return void
* @Author fangzhenxun
* @Description 引數檢查
* @Date 2020/12/30 13:37
**/
private static void checkArg(String arg) {
checkArg(arg, true);
}

/**
* @param arg
* @return void
* @Author fangzhenxun
* @Description 引數檢查
* @Date 2020/12/30 13:37
**/
private static void checkArg(String arg, boolean isZero) {
if (StringUtils.isEmpty(arg) || !isNumber(arg) || (!isZero && (new BigDecimal("0").compareTo(new BigDecimal(arg)) == 0))) {
throw new RuntimeException("非法計算引數!");
}
}





/**
* @param expression "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + log(10,12)"
* @return void
* @Author fangzhenxun
* @Description 校驗公式運算式的合法性
* @Date 2020/12/30 14:23
**/
private static void checkFormulaExpression(String expression) {
//去除空格
expression = expression.replaceAll(" ", "");
//拆分字串
char[] arr = expression.toCharArray();
int len = arr.length;
//前后括號計數,用來判斷括號是否合法
int checkNum = 0;
//數字集合
StringBuffer sb = new StringBuffer();
//字母集合
StringBuffer sb0 = new StringBuffer();
//回圈
for (int i = 0; i < len; i++) {
//判斷當前元素是否是數字
if (Character.isDigit(arr[i]) || arr[i] == '.') {
//把數字和小數點加入到集合中,為了下一步判斷數字是否合法
sb.append(arr[i]);
} else if (Character.isLetter(arr[i])) {
//校驗自定義的公式(自定義的數學運算式都是使用字母拼接起來)
sb0.append(arr[i]);
} else {
//如果sb中有值,取出來判斷這個數字整體是否合法
if (sb.length() > 0) {
if (isNumber(sb.toString())) {
//如果合法,清空,為了判斷下一個
sb.setLength(0);
} else {
throw new RuntimeException("非法數字引數");
}
}

//不是數字為字符,可能是{},[], (), +, - , * , /, % 等,再加上各種自定義的數學運算公式, 或者是其他不合法的字符,接著繼續判斷
if (arr[i] == '+' || arr[i] == '*' || arr[i] == '/' || arr[i] == '%') {
//判斷規則(1.不能位于首位 2.不能位于末尾 3.后面不能有其他運算子,但可以有-號 4.后面不能有后括號)
if (i == 0 || i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')') {
log.error("非法符號 : '+' or '*' or '/' ->" + arr[i]);
throw new RuntimeException("非法符號 : '+' or '*' or '/' ==>" + arr[i]);
}
} else if (arr[i] == '-') {
//減號判斷規則(1.不能位于末尾 2.后面不能有其他運算子,但可以有-號 3.后面不能有后括號)
if (i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')') {
log.error("非法符號 : '-' ->" + arr[i]);
throw new RuntimeException("非法符號 : '-' ==>" + arr[i]);
}
} else if (arr[i] == '(') {
//判斷(括號前面是否有字母
//如果sb0中有字母,取出來判斷這個字串整體是否是自定義的公式字符
if (sb0.length() > 0) {
//從當前匹配的(,找到最近的),然后在求出在 這兩個括號之間以英文逗號隔開的引數個數
int beginIndex = expression.indexOf(arr[i], i);

int endIndex = matchBracketIndex(expression, i, arr[i]);
if (endIndex == -1) {
log.error("非法數學公式符號:==>" + sb0.length());
throw new RuntimeException("非法數學公式符號: ==>" + sb0.length());
}
//截取字串,且分隔匹配的英文逗號
String selfMathBracketContentStr = expression.substring(beginIndex + 1, endIndex);
if (StringUtils.isEmpty(selfMathBracketContentStr)) {
log.error("非法自定義數學公式符號:==>" + sb0.length());
throw new RuntimeException("非法自定義數學公式符號: ==>" + sb0.length());
}
//獲取引數個數
StringBuilder selfMathBracketContentSb = new StringBuilder(selfMathBracketContentStr);
int argCounts = getSelfMathMarkArgCounts(selfMathBracketContentSb, ",");
//校驗自定義公式的合法性
checkSelfMathMark(sb0.toString(), argCounts);
//清空內容
sb0.setLength(0);
}

checkNum++;
//判斷規則(1.不能位于末尾 2.后面不能有+,*,/,%運算子和后括號 3.前面不能為數字)
if (i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')' || (i != 0 && Character.isDigit(arr[i - 1]))) {
log.error("非法符號 : '(' ->" + arr[i]);
throw new RuntimeException("非法符號 : '(' ==>" + arr[i]);
}
} else if (arr[i] == ')') {
checkNum--;
//判定規則(1.不能位于首位 2.后面不能是前括號 3.括號計數不能小于0,小于0說明前面少了前括號)
if (i == 0 || (i < (len - 1) && arr[i + 1] == '(') || checkNum < 0) {
log.error("非法符號 : ')' ->" + arr[i]);
throw new RuntimeException("非法符號 : ')' ==>" + arr[i]);
}
} else if (arr[i] == ',') {
//判定規則,如果有逗號,1,匹配該逗號是否被括號()包著,且左括號當前前面是否是自定義公式
checkComma(expression, i, i);
} else {
//非數字和運算子
log.error("非數字和運算子:==>" + arr[i]);
throw new RuntimeException("非數字和運算子:==>" + arr[i]);
}
}
}
//不為0,說明括號不對等,可能多前括號
if (checkNum != 0) {
//非數字和運算子
log.error("括號個數不匹配");
throw new RuntimeException("括號個數不匹配");
}
}


/**
* @param str 待匹配的字符陣列
* @param currentCommaIndex 當前逗號索引
* @param constCommaIndex 常量逗號索引
* @return void
* @Author fangzhenxun
* @Description 校驗英文逗號
* @Date 2020/12/31 13:24
**/
private static void checkComma(String str, int currentCommaIndex, final int constCommaIndex) {
//從currentCommaIndex索引開始往前找最近的一個左括號(,求出索引,并求出對應的)索引
int beginIndex = indexOfBefore(str, currentCommaIndex, '(');
if (beginIndex == -1) {
log.error("非法逗號!");
throw new RuntimeException("非法逗號!");
}
int endIndex = matchBracketIndex(str, beginIndex, '(');
if (endIndex == -1) {
log.error("非法逗號!");
throw new RuntimeException("非法逗號!");
}
//找到符合的逗號條件
if (endIndex <= constCommaIndex) {
//接著往上找
checkComma(str, beginIndex, constCommaIndex);
} else {
//在從beginIndex索引開始往前找出連續字母的字符,然后拼接
char[] chars = str.toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = beginIndex - 1; i >= 0; i--) {
//如果是字母或者是數字
if (Character.isLetter(chars[i]) || Character.isDigit(chars[i])) {
sb.append(chars[i]);
} else {
break;
}
}
//將sb帶入自定義公式進行校驗
List<String> selfMathFormulaNames = getSelfMathFormulaNames();
if (!selfMathFormulaNames.contains(sb.reverse().toString())) {
throw new RuntimeException("非法逗號!");
}
}
}

/**
* @param str 目標字串 如 abcd((a),(b,bb))dada
* @param endIndex
* @return int
* @Author fangzhenxun
* @Description 回傳從結束索引結束,
* @Date 2020/12/31 13:36
**/
private static int indexOfBefore(String str, int endIndex, char dest) {
char[] chars = str.trim().toCharArray();
int len = chars.length;
int index0 = endIndex;
if (len - 1 < endIndex) {
index0 = len;
}
for (int i = index0 - 1; i >= 0; i--) {
if (chars[i] == dest) {
return i;
}
}
//沒有找到回傳-1
return -1;
}


/**
* @param mathArgStr 引數括號字串 如:log((math(1,6)+2),(8*9)) 中的 ((math(1,6)+2),(8*9))
* @param regex 分隔符 默認一般英文逗號,
* @return void
* @Author fangzhenxun
* @Description 獲取自定義公式的引數個說
* @Date 2020/12/31 10:30
**/
private static int getSelfMathMarkArgCounts(StringBuilder mathArgStr, String regex) {
//找到最近的一個( 索引
int beginIndex = mathArgStr.indexOf("(");
if (beginIndex != -1) {
int endIndex = matchBracketIndex(mathArgStr.toString(), beginIndex, '(');
if (endIndex == -1) {
throw new RuntimeException("非法括號匹配!");
}
mathArgStr.replace(beginIndex, endIndex + 1, "");
return getSelfMathMarkArgCounts(mathArgStr, regex);
} else {
//如果沒有匹配的做擴號,則直接求出引數個數
String[] argsArr = mathArgStr.toString().split(regex, -1);
return argsArr.length;
}
}


/**
* @param mathStr log sin
* @param argCount 2 , 1
* @return boolean
* @Author fangzhenxun
* @Description 校驗數字字符陣列
* @Date 2020/12/30 15:09
**/
private static void checkSelfMathMark(String mathStr, int argCount) {
SelfMathFormulaEnum selfMathFormulaEnum = getSelfMathFormulaEnum(mathStr, argCount);
if (selfMathFormulaEnum == null) {
throw new RuntimeException("自定義數學公式不匹配!");
}
}

/**
* @param s 待匹配的字串
* @param fromIndex 開始索引
* @param leftDest 左括號
* @return int
* @Author fangzhenxun
* @Description 通過當前左括號(的索引 , 找到與之匹配對應的右括號) 的索引
* @Date 2020/12/30 17:23
**/
private static int matchBracketIndex(String s, int fromIndex, char leftDest) {
if (StringUtils.isEmpty(s)) {
return -1;
}
//取出匹配目標的第一個索引
int index0 = s.indexOf(leftDest, fromIndex);
if (index0 != -1) {
//1、申明一個stack
Stack<Character> stack = new Stack<>();
//遍歷s String本質上是char[]
for (int i = index0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '{' || c == '[' || c == '(') {
//如果是{ [ ( 壓入堆疊中
stack.push(c);
} else if (c == '}' || c == ']' || c == ')') {
// } ] ) 進行比對,
if (stack.isEmpty()) {
return -1;
}
char topChar = stack.pop();
if ((topChar == '[' && c == ']') || (topChar == '(' && c == ')') || (topChar == '{') && c == '}') {
if (stack.isEmpty()) {
return i;
} else {
continue;
}
}
} else {
continue;
}
}

}
return -1;
}


/**
* @param v1
* @param v2
* @return java.lang.String
* @Author fangzhenxun
* @Description 兩個數相加
* @Date 2020/12/30 13:42
**/
private static String add(String v1, String v2) {
//校驗引數
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.add(v2Bd).toString();
}

/**
* @param v1
* @param v2
* @return java.lang.String
* @Author fangzhenxun
* @Description 兩個數相減
* @Date 2020/12/30 13:47
**/
private static String sub(String v1, String v2) {
//校驗引數
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.subtract(v2Bd).toString();
}

/**
* @param v1
* @param v2
* @return java.lang.String
* @Author fangzhenxun
* @Description 兩個數向乘
* @Date 2020/12/30 13:49
**/
private static String mul(String v1, String v2) {
//校驗引數
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.multiply(v2Bd).toString();
}

/**
* @param v1
* @param v2
* @return java.lang.String
* @Author fangzhenxun
* @Description 兩個數相除
* @Date 2020/12/30 13:50
**/
private static String div(String v1, String v2) {
//校驗引數
checkArg(v1);
//除數不能為0
checkArg(v2, false);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.divide(v2Bd, 2, RoundingMode.HALF_UP).toString();
}

/**
* @Author fangzhenxun
* @Description v1%v2 取余
* @Date 2021/1/4 22:14
* @param v1
* @param v2
* @return java.lang.String
**/
private static String mod(String v1, String v2) {
//校驗引數
checkArg(v1);
//除數不能為0
checkArg(v2, false);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.remainder(v2Bd).toString();
}




/**
* @param mathFormula 公式字串, 如:"1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + log(10,12)"
* 注意log(10) ==> ln(10) ==> log(e,10) 表示以自然數e為底的對數
* @return java.lang.String
* @Author fangzhenxun
* @Description 使用遞回計算,判斷運算式是否有括號,有括號,則先計算括號內的運算式,無則直接運算結果,
* @Date 2020/12/30 14:07
**/
public static String calculator(String mathFormula) {
if (StringUtils.isEmpty(mathFormula)) {
throw new RuntimeException("非法計算公式!");
}
//替換空格
mathFormula = mathFormula.replaceAll(" ", "");
int bracket = mathFormula.indexOf("[");
int brace = mathFormula.indexOf("{");
if (bracket != -1 || brace != -1) {
//將字串中的"{}"、"[]"替換成"()"
log.info("計算公式:{}", mathFormula);
mathFormula = mathFormula.replaceAll("[\\[\\{]", "(").replaceAll("[\\]\\}]", ")");
log.info("標準數學計算公式 '{,[':" + mathFormula);
}
//校驗公式引數是否合法
checkFormulaExpression(mathFormula);

//==================================================開始計算=============================================
//計算思路:以下是計算順序
// 1,如果有自定義的數學公式,則先計算自定義的公式
// 2, 如果有括號,則先計算括號內的(去括號)
// 3, 沒有括號直接計算
String result0 = calculatorSelfMathFormula(mathFormula);
//結果保留八位小數
return new BigDecimal(result0).setScale(8,BigDecimal.ROUND_HALF_UP).toString();
}

//"1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + max(sin(2)*6,pow(2,3))"
public static void main(String args[]) throws ScriptException {
long beginTime = System.currentTimeMillis();


String calculator2 = MathCalculatorUtil.standardCalculation("3*-2");
String calculator3 = MathCalculatorUtil.standardCalculation("3--2");
String k ="-(2.5)*(-1)+(-1)*2";
String k1 ="-2.5*(2)+(-1)*(-2)";
String k2 ="(-2.5)*(-2)+(-1)*(-2)-2*3";
String re2 = calculator(k2);

String ss22 = "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10)/9 + 2*log(30)+ max(sin(2*(5+2))*6,pow(2*(2+8),3+2)) * max(-1+3,(5-4)) + min(sin(10), sin(20))";
String re0 = calculator(k);
String re1 = calculator(k1);
System.out.println(re0);
// Object eval = scriptEngine.eval(ss23);
// System.out.println(eval);
System.out.println("cost時間:" + (System.currentTimeMillis() - beginTime) + "ms");
}



/**
* @Author fangzhenxun
* @Description 去除自定義公式,todo 待優化
* @Date 2020/12/31 16:49
* @param mathFormula
* @return java.lang.String
**/
private static String calculatorSelfMathFormula(String mathFormula) {
if (StringUtils.isEmpty(mathFormula)) {
throw new RuntimeException("非法引數錯誤!");
}
mathFormula = mathFormula.replaceAll(" ", "");
//去除自定義公式
List<SelfMathFormulaEnum> selfMathFormulaEnums = SelfMathFormulaEnum.getSelfMathFormulas();
boolean flag = false;
for (SelfMathFormulaEnum mathFormulaEnum : selfMathFormulaEnums) {
if (mathFormula.contains(mathFormulaEnum.getFormulaName())) {
flag = true;
break;
}
}
//包含自定義公式
if (flag) {
for (int i = 0; i < selfMathFormulaEnums.size();) {
boolean repeat = false;
SelfMathFormulaEnum mathFormulaEnum = selfMathFormulaEnums.get(i);
//如果該公式運算式包含自定義數學公式 "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + 2*log(30)+ max(sin(2*(5+2))*6,pow(2*(2+8),3+2)) * max(1+3,(3-5)) + min(sin(10), sin(20))"
if (mathFormula.contains(mathFormulaEnum.getFormulaName())) {
//如果匹配到,則獲取第一個自定義的數學公式首字母所在的索引(該索引是格式化后的索引)
int index0 = mathFormula.indexOf(mathFormulaEnum.getFormulaName());
//取出該公式括號中內容字符,不包括左右字符
String left = mathFormula.substring(0, index0);
int index1 = matchBracketIndex(mathFormula, index0, '(');
String right = mathFormula.substring(index1 +1);
String bracketsContent = mathFormula.substring(index0 + mathFormulaEnum.getFormulaNameLength() + 1, index1);
//計算括號中的值,如果該字串又包含其他自定義公式,則遞回繼續計算
//left + result0 + right
mathFormula = left + selfMathCalculation(mathFormulaEnum.getFormulaName(), calculatorSelfMathFormula(bracketsContent)) + right;
repeat = true;
}
if (repeat) {
i = i;
}else {
i++;
}
}
}
//直接進行計算
return standardCalculation(mathFormula);
}

/**
* @Author fangzhenxun
* @Description 自定義公式計算
* @Date 2020/12/31 17:07
* @param mathFormulaName 自定義公式名稱
* @param digitStr 一個具體的數值
* @return java.lang.String
**/
private static String selfMathCalculation(String mathFormulaName, String digitStr) {
double result;
if (StringUtils.isEmpty(digitStr)) {
throw new RuntimeException("非法計算公式引數!");
}
String[] args = digitStr.split(",", -1);

SelfMathFormulaEnum selfMathFormulaEnum = getSelfMathFormulaEnum(mathFormulaName);
if (selfMathFormulaEnum == null) {
throw new RuntimeException("非法數學公式名稱");
}
switch (selfMathFormulaEnum) {
case abs:
result = Math.abs(Double.parseDouble(args[0]));
break;
case acos:
result = Math.acos(Double.parseDouble(args[0]));
break;
case asin:
result = Math.asin(Double.parseDouble(args[0]));
break;
case atan:
result = Math.atan(Double.parseDouble(args[0]));
break;
case ceil:
result = Math.ceil(Double.parseDouble(args[0]));
break;
case cos:
result = Math.cos(Double.parseDouble(args[0]));
break;
case exp:
result = Math.exp(Double.parseDouble(args[0]));
break;
case floor:
result = Math.floor(Double.parseDouble(args[0]));
break;
case log:
result = Math.log(Double.parseDouble(args[0]));
break;
case max:
result = Math.max(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case min:
result = Math.min(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case pow:
result = Math.pow(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case round:
result = Math.round(Double.parseDouble(args[0]));
break;
case sin:
result = Math.sin(Double.parseDouble(args[0]));
break;
case sqrt:
result = Math.sqrt(Double.parseDouble(args[0]));
break;
case tan:
result = Math.tan(Double.parseDouble(args[0]));
break;
default:
throw new RuntimeException("找不到匹配的計算公式!");

}
return String.valueOf(result);
}


/**
* @Author fangzhenxun
* @Description 標準計算,不包含自定義函式, 但包含括號與其他符號運算式
* @Date 2020/12/31 17:02
* @param str
* @return java.lang.String
**/
private static String standardCalculation(String str) {
if (StringUtils.isEmpty(str)) {
log.error("非法計算公式!");
throw new RuntimeException("非法計算公式!");
}
String[] args = str.split(",", -1);
if (args != null && args.length > 0) {
List<String> argResult = new ArrayList<>();
for (String arg : args) {
//每一個arg 都是一個算式(帶上括號的)
//判斷是公式表達是是否存在小括號(優先級)
int hasBrackets = arg.lastIndexOf('(');
if (hasBrackets == -1) {
//沒有小括號,直接計算
argResult.add(cac(arg));
}else {
int cr = arg.indexOf(')', hasBrackets);
String left = arg.substring(0, hasBrackets);
String right = arg.substring(cr + 1);
//如果存在"("提取括號中的運算式
String middle = arg.substring(hasBrackets + 1, cr);
argResult.add(standardCalculation(left + cac(middle) + right));
}
}
return StringUtils.join(argResult, ",");
}
throw new RuntimeException("非法算式引數!");
}






/**
* DESC:計算運算式,判斷是否存在乘除運算,存在則先執行乘除運算,然后執行加減運算,回傳運算結果;
* 不存在,直接運行加減運算,回傳運算結果,
*
* @param str -2.8+8*3/2+0.9 或 -2.8*6
* @return 運算結果
*/
private static String cac(String str) {
//字串中不存在*,/, %
int mulIndex = str.indexOf('*');
int divIndex = str.indexOf('/');
int modIndex = str.indexOf('%');
//只有加法和減法
if (mulIndex == -1 && divIndex == -1 && modIndex == -1) {
return AASOperation(str);
}
String result0 = "0";

//定義先處理的符號索引位置
int index0 = getMin(-1,mulIndex, divIndex, modIndex);
try {
String left = str.substring(0, index0);
String v1 = lastNumber(left);
left = left.substring(0, left.length() - v1.length());
String right = str.substring(index0 + 1);
String v2 = firstNumber(right);
right = right.substring(v2.length());

if (index0 == mulIndex) {
result0 = mul(v1, v2);
} else if(index0 == divIndex) {
result0 = div(v1, v2);
} else if(index0 == modIndex) {
result0 = mod(v1, v2);
}
String s = left + result0 + right;
return cac(left + result0 + right);
}catch (Exception e) {
log.error("數學計算公式錯誤"+ e.getMessage());
throw new RuntimeException("數學計算公式錯誤!");
}
}


/**
* @Author fangzhenxun
* @Description 求給定可變引數中不等于noNum 的最小值
* @Date 2021/1/5 10:49
* @param noNum
* @param a 可變引數
* @return int
**/
private static int getMin(int noNum, int... a){
if (a == null || a.length == 0) {
throw new RuntimeException("非法引數!");
}
int min = a[0];
for (int i = 1; i < a.length; i++) {
if (min ==noNum || (min > a[i] && a[i] != noNum)) {
min = a[i];
}
}
if (min == noNum) {
throw new RuntimeException("非法可變引數!");
}
return min;
}


/**
* @Author fangzhenxun
* @Description 獲得運算式的最后連續合法數字
* @date 2021/1/6 17:49
* @param str
* @return java.lang.String
*/
private static String lastNumber(String str) {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
char c = str.charAt(i);
//包含首字母為-
if (Character.isDigit(c) || (i != 0 && c == '.') || ((i == 0 || operateSet.contains(str.charAt(i -1))) && c == '-')) {
sb.append(c);
}else {
break;
}
}
return sb.reverse().toString();
}


/**
* @Author fangzhenxun
* @Description 獲得運算式的最后連續合法數字
* @date 2021/1/6 17:49
* @param str
* @return java.lang.String
*/
private static String firstNumber(String str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
//包含首字母為-
if (Character.isDigit(c) || (i != 0 && c == '.') || (i == 0 && c == '-')) {
sb.append(c);
}else {
break;
}
}
return sb.toString();
}



/**
* @Author fangzhenxun
* @Description 只用加減操作
* @Date 2021/1/4 20:02
* @param mathStr 只有加減操作的數學運算字串: 如2.98-5-6+9-0.2-8 或 -2.0-9-5+9 或 -9-2
* @return java.lang.String
**/
private static String AASOperation(String mathStr) {
if (StringUtils.isEmpty(mathStr)) {
throw new RuntimeException("非法計算引數");
}
//這里字串加上一個運演算法號,只要是合法的都可以,只是為了走一步運算
char[] options = (mathStr + "+").replaceAll(" ", "").toCharArray();
String result0 = "0";
StringBuilder sb = new StringBuilder();
char sign = '+';
for (int i = 0; i < options.length; i++) {
if (Character.isDigit(options[i]) || options[i] == '.') {
sb.append(options[i]);
} else {
if ((i == 0 && options[i] == '-') || (i>1 && operateSet.contains(options[i-1]))) {
sb.append(options[i]);
}else {
if (sb.length() > 0){
//先默認為 + 把第一個數值加上
if (sign == '+') {
result0 = add(result0, sb.toString());
} else {
result0 = sub(result0, sb.toString());
}
sb.setLength(0);
sign = options[i];
} else {
throw new RuntimeException("非法數學公式錯誤!");
}
}

}
}

return result0;
}}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/245977.html

標籤:其他

上一篇:scrapy爬蟲框架你還不會嗎?簡單使用爬蟲框架采集網站資料

下一篇:阿里面試官親述:如何利用設計模式改善業務代碼

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more