直接上一個之前寫過的工具類,無依賴扼褪,全部邏輯都在方法上有注釋想幻。
public class FormulaCalcUtil {
/**
* 數(shù)學的基礎運算符號,按照優(yōu)先級排序放入數(shù)組;
* 乘號與除號是同一優(yōu)先級话浇;加號與減號是同一優(yōu)先級
*/
private final static char MathSymbolArr[][] = {{'*', '/'}, {'+', '-'}};
private final static char leftBracket = '(';
private final static char rightBracket = ')';
/**簡單運算的結(jié)果
*/
private static float executeResult = 0.0f;
/**
* 表達式的計算結(jié)果
*/
private static float result = 0.0f;
/**
* 計算一個簡單的運算
*
* @param operator 數(shù)學運算符
* @param left 計算公式左側(cè)數(shù)字
* @param right 計算公式右側(cè)數(shù)字
* @return
*/
private static Float baseExecute(char operator, Float left, Float right) {
switch (operator) {
case ('*'):
return left * right;
case ('/'):
return left / right;
case ('+'):
return left + right;
case ('-'):
return left - right;
default:
throw new IllegalArgumentException("Invalid operator: " + operator);
}
}
/**
* 判斷公式是否需要計算
*
* @param expression 數(shù)學表達式
* @return
*/
public boolean needCalculate(String expression) {
if (expression.contains("\\*") || expression.contains("\\/")
|| expression.contains("\\+") || expression.contains("\\-")) {
return true;
}
return false;
}
/**
* 判斷運算式是否合法(主要是判斷括號是否是成對存在)
*
* @param expression 數(shù)學公式的字符串
* @return
*/
public static boolean isValidExpression(String expression) {
Stack<Character> stack = new Stack<>();
for (char c : expression.toCharArray()) {
if (c == leftBracket) {
stack.push('(');
} else if (c == rightBracket) {
if (stack.isEmpty()) {
return false;
}
stack.pop();
}
}
return stack.isEmpty();
}
/**
* 計算運算式是否有括號
* 注:
* 已經(jīng)確定了公式的合法性脏毯;
*
* @param expression
* @return
*/
public static boolean hasBracket(String expression) {
boolean hasBracket = false;
for (char c : expression.toCharArray()) {
if (c == leftBracket) {
hasBracket = true;
break;
}
}
return hasBracket;
}
/**
* 判斷表達式是否有基礎運算符
*
* @param expression
* @return
*/
public static boolean hasOperator(String expression) {
boolean hasOperator = false;
if (null != expression) {
loop:
for (char[] mathSymbol : MathSymbolArr) {
for (char symbol : mathSymbol) {
for (char expressChar : expression.toCharArray()) {
if (symbol == expressChar) {
hasOperator = true;
break loop;
}
}
}
}
}
return hasOperator;
}
/**
* 完整公式的計算過程
* 1 將表達式轉(zhuǎn)成字節(jié)數(shù)組;
* 2 遍歷表達式字節(jié)數(shù)組幔崖,所有左括號和右括號的index食店,存入數(shù)組
* 左括號用先進后出的數(shù)組;右括號用先進先出的數(shù)組;
* 但是赏寇,如果查到左括號的時候吉嫩,右括號數(shù)組已經(jīng)有內(nèi)容,說明存在同一層級的括號嗅定,那么對括號優(yōu)先級的判斷到此為止自娩,先計算已經(jīng)統(tǒng)計的最內(nèi)層括號;
* 3 找出最內(nèi)層括號的簡單表達式渠退,通過 @method{executeWithNoBracket}計算
* 4 重復步驟2到步驟3忙迁,直到?jīng)]有括號
* <p>
* 注:表達式最外層不允許有括號
* todo 該方法還需優(yōu)化
* @param expression
* @return
*/
public static Float execute(String expression) {
if (!hasBracket(expression) && !hasOperator(expression)) {
return Float.valueOf(expression);
}
char[] chars = expression.toCharArray();
// 存儲左括號的index
List<Integer> leftList = new ArrayList<>();
// 右側(cè)括號的index
int rightIndex = 0;
// 提取到最內(nèi)層的括號
loop:
while (hasBracket(expression)) {
for (int i = 0; i < chars.length; i++) {
// 只要找到右側(cè)的第一個括號的位置就跳出括號統(tǒng)計的循環(huán)脐彩,開始計算
if (chars[i] == '(' && rightIndex == 0) {
leftList.add(i);
}
if (chars[i] == ')') {
rightIndex = i;
break loop;
}
}
}
/**
* 提取到最內(nèi)層括號后開始計算最內(nèi)層公式;
* 括號內(nèi)的公式提取要去除左右括號动漾;
* leftList.get(leftSize-1) 是最內(nèi)層左括號的index丁屎;leftList.get(leftSize-1)+1 是避開了左括號的位置;
* rightIndex 是最內(nèi)層右括號的index;
*/
if (rightIndex > 0) {
int leftSize = leftList.size();
String expressionNoBrackets = expression.substring(leftList.get(leftSize - 1) + 1, rightIndex);
result = executeWithNoBracket(expressionNoBrackets);
expression = replaceExpress(expression, leftList.get(leftSize - 1), rightIndex, result);
execute(expression);
}
if (hasOperator(expression)) {
result = executeWithNoBracket(expression);
}
return result;
}
/**
* 計算沒有括號的運算公式
* 邏輯思路:
* 按照運算公式本身的順序荠锭,根據(jù)數(shù)學符號的優(yōu)先級旱眯,逐步計算
* <p>
* 具體步驟:
* 1 遍歷數(shù)學符號的字節(jié)數(shù)組 {MathSymbolArr},該數(shù)組已經(jīng)定義了運算符號的優(yōu)先級证九;
* 2 數(shù)學公式{expressionNoBracket}轉(zhuǎn)為字節(jié)數(shù)組{expressArr},遍歷該字節(jié)數(shù)組删豺,找到與當前運算符匹配的運算符;如果沒有愧怜,繼續(xù)下一個運算符呀页。
* 3 找到對應運算符后,以該運算符為索引拥坛,查找運算符兩端的數(shù)字蓬蝶;
* 左側(cè)數(shù)字:從運算符開始往前查找,直到找到上一個運算符猜惋,或者找到該表達式的起始位置丸氛;且每個位置上的字符c屬于[0,9]區(qū)間或是小數(shù)點;
* 右側(cè)數(shù)字:從運算符開始往后查找著摔,直到找到下一個運算符缓窜,或者找到該表達式的結(jié)束為止;且每個位置上的字符c屬于[0,9]區(qū)間或是小數(shù)點谍咆;
* <p>
* 4 將獲取到的數(shù)值和運算符進行基礎運算禾锤,帶入 {baseExecute(char operator, Float left, Float right)};
* 5 將基礎運算的結(jié)果,替換到未運算完成的公式摹察,得到新的需要運算的表達式恩掷;
* 只要進行過一次運算,就賦值得到新的表達式供嚎;
* 并跳出內(nèi)外循環(huán)(表達式字節(jié)數(shù)組的循環(huán)到此終止黄娘;基礎運算符號的循環(huán)也終止);
* <p>
* 6 計算接下來的公式查坪;重復步驟1到步驟4寸宏,直到表達式中不存在運算符,計算結(jié)束偿曙。
*
* @param expressionNoBracket
* @return
*/
private static Float executeWithNoBracket(String expressionNoBracket) {
if (!hasOperator(expressionNoBracket)) {
return Float.valueOf(expressionNoBracket);
}
char[] expressCharArr = expressionNoBracket.toCharArray();
int opIndex = 0;
/*
* 無括號的表達式氮凝,優(yōu)先級排序: 乘號 = 除號 > 加號 = 減號;
* 同一優(yōu)先級望忆,先查到哪個符號就計算那個符號;
* 每次循環(huán)只進行一次基礎計算
*/
loop:
for (char[] chars : MathSymbolArr) {
char[] mathSymbol = chars;
for (int expIndex = 0; expIndex < expressCharArr.length; expIndex++) {
// 找到運算符的位置罩阵, 每個運算符只做一次計算最欠,然后更新表達式
if (expressCharArr[expIndex] == mathSymbol[0] || expressCharArr[expIndex] == mathSymbol[1]) {
opIndex = expIndex;
break loop;
}
}
}
String left = getLeftNumber(expressionNoBracket, 0, opIndex - 1);
String right = getRightNumber(expressionNoBracket, opIndex + 1, expressionNoBracket.length() - 1);
// 只有兩個數(shù)值的表達式簡單運算
executeResult = baseExecute(expressCharArr[opIndex], Float.valueOf(left), Float.valueOf(right));
// 帶入剛計算出的值苛萎,更新表達式
expressionNoBracket = replaceExpress(expressionNoBracket, opIndex - left.length(), opIndex + right.length(), executeResult);
if (hasOperator(expressionNoBracket)) {
executeWithNoBracket(expressionNoBracket);
}
return executeResult;
}
/**
* 通過上一步計算出的結(jié)果覆蓋公式中的基礎運算式
* 如
* 原式 2*3+1;上一步計算出2*3 = 6;
* 通過 nextExpress(2*3+1, 0, 2, 6) 得出新的公式為 6 + 1
*
* @param expression 被替換的表達式
* @param start 替換開始的位置(該位置也替換)
* @param end 替換結(jié)束的位置(該位置也替換)
* @param executeResult 用來替換的數(shù)值
* @return
*/
private static String replaceExpress(String expression, int start, int end, Float executeResult) {
return expression.substring(0, start) + executeResult + expression.substring(end + 1);
}
/**
* 找到運算符右邊的數(shù)值
*
* @param expression 原表達式
* @param start 表達式右側(cè)數(shù)值的開始位置
* @param end 表達式結(jié)束的位置
* @return
*/
private static String getRightNumber(String expression, int start, int end) {
char[] chars = expression.toCharArray();
int bottom = 0;
for (int i = start; i <= end && (chars[i] >= '0' && chars[i] <= '9' || chars[i] == '.'); i++) {
bottom = i;
}
return expression.substring(start, bottom + 1);
}
/**
* 找到運算符左邊的數(shù)值
*
* @param expression 原表達式
* @param start 表達式開始的位置
* @param end 計算右側(cè)數(shù)值的終止位置
* @return
*/
private static String getLeftNumber(String expression, int start, int end) {
char[] chars = expression.toCharArray();
int bottom = 0;
for (int i = end; i >= start && (chars[i] >= '0' && chars[i] <= '9' || chars[i] == '.'); i--) {
bottom = i;
}
return expression.substring(bottom, end + 1);
}
public static void main(String[] args) {
/**
* 測試1:左右側(cè)數(shù)值的提取
*/
/*String expression = "9*0.01";
System.out.println("leftNumber = " + getLeftNumber(expression, 0, 0));
System.out.println("rightNumber = " + getRightNumber(expression, 2, 5));
*//**
* 測試2:表達式替換
*//*
String expression = "1+6.0+1";
System.out.println(replaceExpress(expression, 2, 4, (float) 6));
System.out.println(hasOperator("11.23"));
*//**
* 測試3:基礎運算
*//*
String expression = "20*2/5-0.77+2*2";
Float execute = executeWithNoBracket(expression);
System.out.println("計算結(jié)果:" + execute)*/
/**
* 測試4:帶括號的計算
*/
String expression = "((2*3+1+8)*5+1)/2";
Float execute = execute(expression);
System.out.println("計算結(jié)果:" + execute);
}
}
這個實現(xiàn)沒有好好的使用棧的功能益缠,完全是按照最簡單的方法實現(xiàn)的。
而實際上团驱,java本身就可以實現(xiàn)這些簡單的四則運算构订,直接將公式作為腳本使用就行。
float formula = (float) (((2*3+1+8)*5+1)/2);
System.out.println(formula);