最近做一個項目需要做表達式的解析,初想不難,仔細研究之後,發現做細點可能會涉及到編譯原理的詞法解析和語法解析。但是如果只做簡單的表達式計算,可以使用逆波蘭式。
何爲逆波蘭式,可以看這個鏈接:https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437?fr=aladdin
簡單研究了一下,下面是實現的簡單四則運算:
public class RPNUtils {
private static final Logger logger = LoggerFactory.getLogger(RPNUtils.class);
/**
* 運算符優先級map(數字越大,優先級越高)
*/
static Map<String,Integer> priorityMap = new HashMap<>(4);
static{
priorityMap.put("+",1);
priorityMap.put("-",1);
priorityMap.put("*",2);
priorityMap.put("/",2);
priorityMap.put("(",0);
}
/**
* 計算表達式
* @param exp(中綴表達式)
* @return 計算結果
*/
public static Double calExp(String exp){
try{
logger.info("傳入計算表達式:{}", exp);
List<String> rpnList = transToRPN(exp);
logger.info("逆波蘭式爲:{}",JSON.toJSONString(rpnList));
Double result = cal(rpnList);
logger.info("計算結果爲:{}",result);
return result;
}catch (Exception e){
e.printStackTrace();
logger.error("表達式計算異常:"+exp,e);
}
return 0.0;
}
/**
* 轉化成逆波蘭式
* @param exp
* @return
*/
public static List<String> transToRPN(String exp){
//操作數棧
Stack<String> numStack = new Stack<>();
//運算符棧
Stack<String> operStack = new Stack<>();
//轉化成字符數組
char[] expArray = exp.toCharArray();
StringBuffer sb = new StringBuffer();
List<String> numOperList = new ArrayList<>();
//分離運算符和操作數(在運算符和操作數之間添加空格符)
for (int i = 0; i < expArray.length; i++){
if (Character.isDigit(expArray[i]) || ".".equals(String.valueOf(expArray[i]))){
sb.append(expArray[i]);
}else{
sb.append(" ").append(expArray[i]).append(" ");
}
}
//得到操作數和運算符的數組
String[] array = sb.toString().trim().split(" ");
//轉化成list
numOperList = Arrays.asList(array);
for (int i = 0; i < numOperList.size(); i++) {
String a = numOperList.get(i);
//過濾空字符串
if (a.equals("")){
continue;
}
if (Character.isDigit(a.charAt(0))){
//如果是操作數直接放入操作數棧
numStack.push(a);
}else{
if (operStack.isEmpty()){
//如果是運算符,且運算符棧是空的,直接將運算符放入運算符棧
operStack.push(a);
continue;
}
if (a.equals("(")){
//如果是左括號,直接放入運算符棧
operStack.push(a);
continue;
}
if (a.equals(")")){
//如果是右括號,則運算符棧依次出棧,並放入操作數棧,直到出棧運算符爲左括號,並捨棄左括號
while (!operStack.peek().toString().equals("(")){
numStack.push(operStack.pop());
}
operStack.pop();
continue;
}
//比較當前運算符和運算符棧頂的運算符
//如果棧頂運算符爲左括號,則直接放入運算符棧
String topOper = operStack.peek();
if (topOper.equals("(")){
operStack.push(a);
continue;
}
//比較當前運算符的優先級和運算符棧頂運算符的優先級,若大於(等於)棧頂運算符優先級,則直接放入運算符棧
if (priorityMap.get(a.toString()) > priorityMap.get(topOper.toString())){
operStack.push(a);
}else{
// 否則,運算符棧頂運算符出棧並放入操作數棧,直到運算符棧頂操作符優先級低於(不包含等於)該運算符優先級
do{
numStack.push(operStack.pop());
}while(!operStack.isEmpty() && priorityMap.get(operStack.peek()) >= priorityMap.get(a));
//最後當前運算符放入運算符棧
operStack.push(a);
}
}
}
//如果上面步驟走完了,運算符棧中還有運算符,則依次出棧,放入到操作數棧
while(!operStack.isEmpty()){
numStack.push(operStack.pop());
}
return new ArrayList<>(numStack);
}
/**
* 根據逆波蘭式計算表達式結果
* @param list
* @return
*/
public static Double cal(List<String> list){
System.out.println(JSON.toJSONString(list));
//計算棧
Stack<String> s = new Stack<>();
for (int i = 0; i < list.size(); i++) {
String a = list.get(i);
if (Character.isDigit(a.charAt(0))){
//如果是數字,則直接入棧
s.push(a);
}else{
//如果是操作符,則計算棧棧頂兩個元素依次出棧,進行計算,然後將計算結果入棧
if (a.equals("+")){
Double result = Double.valueOf(s.pop()) + Double.valueOf(s.pop());
s.push(String.valueOf(result));
}
if (a.equals("-")){
Double s1 = Double.valueOf(s.pop());
Double s2 = Double.valueOf(s.pop());
s.push(String.valueOf(s2 - s1));
}
if (a.equals("*")){
Double s1 = Double.valueOf(s.pop());
Double s2 = Double.valueOf(s.pop());
s.push(String.valueOf(s1*s2));
}
if (a.equals("/")){
Double s1 = Double.valueOf(s.pop());
Double s2 = Double.valueOf(s.pop());
s.push(String.valueOf(s2 / s1));
}
}
}
//計算完成,棧中元素便是計算結果
return Double.valueOf(s.pop());
}
public static void main(String[] args) {
//String str = "(50-50*50/50)*5.1/5.1+50*20+15.5*(20-10)/5.0-(2-1)*(5/1.0)-2+(2*2)/ 4.0";
//String str = "10-1*5-2";
String str = "(10-3-2-3-1)*(20-10-5)*(3-2-2+2+4)";
System.out.println(calExp(str));
}
}