1. 程式人生 > >JAVA四則運算字串直譯器

JAVA四則運算字串直譯器

最近學習到字尾表示式,於是基於字尾表示式的思想,寫了一個四則運算直譯器,輸入字串型別的四則運算表示式,可以直接得到結果,支援括號巢狀.  實現時主要考慮以下兩點:

  • 字串中運算子和數字分離
  • 運算子優先順序
  • 括號的巢狀
  1. 運算子和數字分離:可以考慮用字串陣列儲存
  2. 關於運算子優先順序,最開始的想法是將乘除法看作一類,加減法看作一類,乘除法的優先順序大於加減法,相同型別的運算子按照從左到右順序依次計算.
  3. 括號的巢狀:由於括號內部本身也是表示式,因此可以使用遞迴處理,但重點在於括號的配對,由於使用遞迴,所以希望獲取最大的巢狀單元.

具體實現:

首先實現運算子識別的程式碼,判斷一個字元是否為運算子:

public static boolean opjudge(char c) {
        if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
            return true;
        else
            return false;
    }

然後實現字串表示式轉為字串陣列, 由於要求輸入的字串運算子和數字之間沒有空格,所以直接拆分不方便,而且運算子和數字的數目不確定,因此先使用ArrayList儲存,然後再轉成字串陣列,主要方法是遍歷字串中每一個字元並判斷是否為運算子,如果是運算子,則直接將運算子轉為字串加入ArrayList,如果不是,則從此位置繼續查詢到下一個運算子出現的位置,兩個位置之間即為運算元,使用substring擷取字串存入Arraylist,具體實現如下:

複製程式碼

public static String[] convert(String s) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            if (!opjudge(s.charAt(i))) {
                int j = i;
                while ((i < s.length()) && !opjudge(s.charAt(i)))
                    i++;
                arrayList.add(s.substring(j, i));
                i--;
            } else
                arrayList.add(String.valueOf(s.charAt(i)));
        }

        return arrayList.toArray(new String[arrayList.size()]);

    }

複製程式碼

然後對運算子進行分類和分級,加減法視作一類,乘除法視作一類,乘除法優先順序高於加減法:

複製程式碼

 public static int opvalue(String s) {
        switch (s) {
        case "+":
        case "-":
            return 1;
        case "*":
        case "/":
            return 2;
        default:
            return -1;
        }

    }

複製程式碼

運算子優先順序的比較,左邊大於右邊則返回true,( 兩個運算子相等返回false)

複製程式碼

 public static boolean opcompare(String s1, String s2) {
        if (opvalue(s1) > opvalue(s2))
            return true;
        else {
            return false;
        }

    }

複製程式碼

括號內字串的獲取:  由於括號都是成對出現,要從左至右獲取第一個最長的括號表示式,則從左至右遍歷字串時,當右括號 ‘)’ 出現的數目等於左括號’(’ 時,即為所求,此處使用計數器實現,返回的是字串中從k位置(第k個元素,一個數字算一個元素 )開始第一個帶完整括號的子串,比如輸入bracketGet(“(8-(2+3))*2+(5+7)*3”,0),返回8-(2+3):

複製程式碼

public static String bracketGet(String s, int k) {
        int m=0;
        int i;
        String[] arr=convert(s);
        for(i=k;i<arr.length;i++){
            if(arr[i].equals("(")){
                m++;
                continue;
                }
            if(arr[i].equals(")")){
                m--;
                if(m==0)
                    break;
                else
                    continue;
            }

        }
        StringBuilder sb=new StringBuilder();
        for(int j=k+1;j<i;j++){
            sb.append(arr[j]);
        }
        return sb.toString();
    }

複製程式碼

以上都是需要用到的配件,下面將利用上面寫好的函式實現四則運算字串的解析和運算,基本思想是使用兩個棧,一個儲存數字,一個儲存運算子,先考慮沒有括號的簡單情況:  遍歷字串陣列:  1.當前元素為運算元時,入數字棧  2.當前元素為運算子時:

  • 如果運算子棧為空,則入運算子棧
  • 如果運算子棧不空,則比較當前運算子和棧頂運算子優先順序(按照之前定義的優先順序,乘除大於加減,乘除相同,加減相同):如果當前運算子優先順序大於棧頂運算子,則當前運算子入棧;反之,如果當前運算子優先順序等於或小於棧頂運算子,則將棧頂運算子出棧,同時數字棧中出棧兩個數字參與運算,結果壓入數字棧中,然後當前運算子入棧;
  • 最後將運算子棧中所有元素依次出棧,數字棧中相應將數字出棧參與運算得到結果並壓入棧中;
  • 最後返回數字棧中棧頂元素即為表示式結果.
  • 有括號時括號內的運算步驟同上,可以獲取括號內部的字串,使用遞迴計算;  程式碼如下:
  • 複製程式碼

    public static int caculate(String formula) {
            String[] arr = convert(formula);
            Stack<Integer> val = new Stack<>();
            Stack<String> op = new Stack<>();
    
            for (int i = 0; i < arr.length; i++) {
                if (arr[i].equals("(")) {
                    val.push(caculate(bracketGet(formula, i)));//遞迴計算括號內部的值,並將該值入棧
                    i = i + bracketGet(formula, i).length() + 1;//括號串所在的位置遍歷過,i需要跳過該段
                } else {
                    if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) {
                        if (op.isEmpty())
                            op.push(arr[i]);
                        else if (opcompare(arr[i], op.lastElement())) {
                            op.push(arr[i]);
                        } else {
    
                            switch (op.pop()) {
                            case "+":
                                val.push(val.pop() + val.pop());
                                break;
                            case "-":
                                int c = val.pop();
                                int d = val.pop();  
                                val.push(d-c);//減法,pop的順序從右至左,所以需要先取出元素
                                break;
                            case "*":
                                val.push(val.pop() * val.pop());
                                break;
                            case "/":
                                int a = val.pop();
                                int b = val.pop();
                                val.push(b / a);//同減法
                                break;
                            default:
                                break;
                            }
                            op.push(arr[i]);
                        }
                    } else
                        val.push(Integer.parseInt(arr[i]));
                }
            }
            while (!op.isEmpty()) {
                switch (op.pop()) {
                case "+":
                    val.push(val.pop() + val.pop());
                    break;
                case "-":
                    int c = val.pop();
                    int d = val.pop();
                    val.push(d-c);
                    break;
                case "*":
                    val.push(val.pop() * val.pop());
                    break;
                case "/":
                    int a = val.pop();
                    int b = val.pop();
                    val.push(b / a);
                    break;
                default:
                    break;
                }
            }
    
            return val.pop();
        }
    System.out.println(caculate("(((8-(2+3))*2+(5+7)*3))"));
    輸出:42 正確

    複製程式碼

    以上實現是使用int型別,除法可能會不準確,改為double會準確

    後面又發現上面的實現方法仍然有冗餘之處,下面是改進的程式碼,主要是優化了運算子的優先順序,依次為 除法 乘法 減法 加法 依次降低, 遍歷到運算子時,優先順序高才入棧, 否則將棧中運算子出棧參與運算,直到棧空或 棧頂運算子優先順序低於遍歷到的運算子,才將該運算子入棧.  主要改動的是opjudge(),opcompare(), caculate()中少量改動,其餘程式碼同上;  具體程式碼如下:

  • 複製程式碼

    public static int opvalue2(String s) {
            switch (s) {
            case "+":
                return 1;
            case "-":
                return 2;
            case "*":
                return 3;
            case "/":
                return 4;
            default:
                return -1;
            }
        }

    複製程式碼

    複製程式碼

    public static boolean opcompare2(String s1, String s2) {
            if (opvalue2(s1) >= opvalue2(s2))
                return true;
            else {
                return false;
            }
        }

    複製程式碼

    複製程式碼

    public static double caculate2(String formula) {
            String[] arr = convert(formula);
            Stack<Double> val = new Stack<>();
            Stack<String> op = new Stack<>();
    
            for (int i = 0; i < arr.length; i++) {
                if (arr[i].equals("(")) {
                    val.push(caculate2(bracketGet(formula, i)));
                    i = i + bracketGet(formula, i).length() + 1;
                } else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) {
                            while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) {
                                switch (op.pop()) {
                                case "+":
                                    val.push(val.pop() + val.pop());
                                    continue;
                                case "-":
                                    double c = val.pop();
                                    double d = val.pop();
                                    val.push(d - c);
                                    continue;
                                case "*":
                                    val.push(val.pop() * val.pop());
                                    continue;
                                case "/":
                                    double a = val.pop();
                                    double b = val.pop();
                                    val.push(b / a);
                                    continue;
                                default:
                                    break;
                                }
                            }
                            op.push(arr[i]);        
                    } 
                    else
                        val.push(Double.parseDouble(arr[i]));
            }
            while (!op.isEmpty()) {
                switch (op.pop()) {
                case "+":
                    val.push(val.pop() + val.pop());
                    break;
                case "-":
                    double c = val.pop();
                    double d = val.pop();
                    val.push(d - c);
                    break;
                case "*":
                    val.push(val.pop() * val.pop());
                    break;
                case "/":
                    double a = val.pop();
                    double b = val.pop();
                    val.push(b / a);
                    break;
                default:
                    break;
                }
            }
            return val.pop();
        }

    複製程式碼

    運算例項:

  • 複製程式碼

    public static void main(String[] args) {
            double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3))));
            System.out.println(a);
            System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))"));
        }
    //output:
    -9.2
    -9.2

    複製程式碼

    至此,完成了四則運算字串直譯器,還有很多可以完善, 比如異常的處理,以及程式碼的簡潔性,作為一個初學者,如果能被看到的話,希望多提建議:

    完整版程式碼如下:

  • 複製程式碼

    import java.util.ArrayList;
    import java.util.Stack;
    
    public class Main {
    
        public static void main(String[] args) {
            double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3))));
            System.out.println(a);
            System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))"));
        }
    
        //對運算子優先順序進一步排序  減法大於加法   除法大於乘法  
        public static double caculate2(String formula) {
            String[] arr = convert(formula);
            Stack<Double> val = new Stack<>();
            Stack<String> op = new Stack<>();
    
            for (int i = 0; i < arr.length; i++) {
                if (arr[i].equals("(")) {
                    val.push(caculate2(bracketGet(formula, i)));
                    i = i + bracketGet(formula, i).length() + 1;
                } else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) {
                            while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) {
                                switch (op.pop()) {
                                case "+":
                                    val.push(val.pop() + val.pop());
                                    continue;
                                case "-":
                                    double c = val.pop();
                                    double d = val.pop();
                                    val.push(d - c);
                                    continue;
                                case "*":
                                    val.push(val.pop() * val.pop());
                                    continue;
                                case "/":
                                    double a = val.pop();
                                    double b = val.pop();
                                    val.push(b / a);
                                    continue;
                                default:
                                    break;
                                }
                            }
                            op.push(arr[i]);        
                    } 
                    else
                        val.push(Double.parseDouble(arr[i]));
            }
            while (!op.isEmpty()) {
                switch (op.pop()) {
                case "+":
                    val.push(val.pop() + val.pop());
                    break;
                case "-":
                    double c = val.pop();
                    double d = val.pop();
                    val.push(d - c);
                    break;
                case "*":
                    val.push(val.pop() * val.pop());
                    break;
                case "/":
                    double a = val.pop();
                    double b = val.pop();
                    val.push(b / a);
                    break;
                default:
                    break;
                }
            }
            return val.pop();
        }
    
    public static String bracketGet(String s, int k) {
            int m=0;
            int i;
            String[] arr=convert(s);
            for(i=k;i<arr.length;i++){
                if(arr[i].equals("(")){
                    m++;
                    continue;
                    }
                if(arr[i].equals(")")){
                    m--;
                    if(m==0)
                        break;
                    else
                        continue;
                }
            }
            StringBuilder sb=new StringBuilder();
            for(int j=k+1;j<i;j++){
                sb.append(arr[j]);
            }
            return sb.toString();
        }
    
    public static String[] convert(String s) {
            ArrayList<String> arrayList = new ArrayList<>();
            for (int i = 0; i < s.length(); i++) {
                if (!opjudge(s.charAt(i))) {
                    int j = i;
                    while ((i < s.length()) && !opjudge(s.charAt(i)))
                        i++;
                    arrayList.add(s.substring(j, i));
                    i--;
                } else
                    arrayList.add(String.valueOf(s.charAt(i)));
            }
            return arrayList.toArray(new String[arrayList.size()]);
        }
    
        public static boolean opjudge(char c) {
        if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
            return true;
        else
            return false;
    }
    
        public static int opvalue2(String s) {
            switch (s) {
            case "+":
                return 1;
            case "-":
                return 2;
            case "*":
                return 3;
            case "/":
                return 4;
            default:
                return -1;
            }
        }
    
        public static boolean opcompare2(String s1, String s2) {
            if (opvalue2(s1) >= opvalue2(s2))
                return true;
            else {
                return false;
            }
        }

    複製程式碼

最近學習到字尾表示式,於是基於字尾表示式的思想,寫了一個四則運算直譯器,輸入字串型別的四則運算表示式,可以直接得到結果,支援括號巢狀.  實現時主要考慮以下兩點:

  • 字串中運算子和數字分離
  • 運算子優先順序
  • 括號的巢狀
  1. 運算子和數字分離:可以考慮用字串陣列儲存
  2. 關於運算子優先順序,最開始的想法是將乘除法看作一類,加減法看作一類,乘除法的優先順序大於加減法,相同型別的運算子按照從左到右順序依次計算.
  3. 括號的巢狀:由於括號內部本身也是表示式,因此可以使用遞迴處理,但重點在於括號的配對,由於使用遞迴,所以希望獲取最大的巢狀單元.

具體實現:

首先實現運算子識別的程式碼,判斷一個字元是否為運算子:

public static boolean opjudge(char c) {
        if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
            return true;
        else
            return false;
    }

然後實現字串表示式轉為字串陣列, 由於要求輸入的字串運算子和數字之間沒有空格,所以直接拆分不方便,而且運算子和數字的數目不確定,因此先使用ArrayList儲存,然後再轉成字串陣列,主要方法是遍歷字串中每一個字元並判斷是否為運算子,如果是運算子,則直接將運算子轉為字串加入ArrayList,如果不是,則從此位置繼續查詢到下一個運算子出現的位置,兩個位置之間即為運算元,使用substring擷取字串存入Arraylist,具體實現如下:

    public static String[] convert(String s) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            if (!opjudge(s.charAt(i))) {
                int j = i;
                while ((i < s.length()) && !opjudge(s.charAt(i)))
                    i++;
                arrayList.add(s.substring(j, i));
                i--;
            } else
                arrayList.add(String.valueOf(s.charAt(i)));
        }

        return arrayList.toArray(new String[arrayList.size()]);

   :
    public static void main(String[] args) {
        double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3))));
        System.out.println(a);
        System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))"));
    }
//output:
-9.2
-9    }

    //對運算子優先順序進一步排序  減法大於加法   除法大於乘法  
    public static double caculate2(String formula) {
        String[] arr = convert(formula);
        Stack<Double> val = new Stack<>();
        Stack<String> op = new Stack<>();

        for (int i = 0; i < arr.length; i++) {
            if (arr[i].equals("(")) {
                val.push(caculate2(bracketGet(formula, i)));
                i = i + bracketGet(formula, i).length() + 1;
            } else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) {
                        while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) {
                            switch (op.pop()) {
                            case "+":
                                val.push(val.pop() + val.pop());
                                continue;
                            case "-":
                                double c = val.pop();
                                double d = val.pop();
                                val.push(d - c);
                                continue;
                            case "*":
                                val.push(val.pop() * val.pop());
                                continue;
                            case "/":
                                double a = val.pop();
                                double b = val.pop();
                                val.push(b / a);
                                continue;
                            default:
                                break;
                            }
                        }
                        op.push(arr[i]);        
                } 
                else
                    val.push(Double.parseDouble(arr[i]));

        }
        while (!op.isEmpty()) {
            switch (op.pop()) {
            case "+":
                val.push(val.pop() + val.pop());
                break;
            case "-":
                double c = val.pop();
                double d = val.pop();
                val.push(d - c);
                break;
            case "*":
                val.push(val.pop() * val.pop());
                break;
            case "/":
                double a = val.pop();
                double b = val.pop();
                val.push(b / a);
                break;
            default:
                break;
            }
        }

        return val.pop();
    }

public static String bracketGet(String s, int k) {
        int m=0;
        int i;
        String[] arr=convert(s);
        for(i=k;i<arr.length;i++){
            if(arr[i].equals("(")){
                m++;
                continue;
                }
            if(arr[i].equals(")")){
                m--;
                if(m==0)
                    break;
                else
                    continue;
            }

        }
        StringBuilder sb=new StringBuilder();
        for(int j=k+1;j<i;j++){
            sb.append(arr[j]);
        }
        return sb.toString();
    }

public static String[] convert(String s) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            if (!opjudge(s.charAt(i))) {
                int j = i;
                while ((i < s.length()) && !opjudge(s.charAt(i)))
                    i++;
                arrayList.add(s.substring(j, i));
                i--;
            } else
                arrayList.add(String.valueOf(s.charAt(i)));
        }

        return arrayList.toArray(new String[arrayList.size()]);

    }

    public static boolean opjudge(char c) {
    if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
        return true;
    else
        return false;
}

    public static int opvalue2(String s) {
        switch (s) {
        case "+":
            return 1;
        case "-":
            return 2;
        case "*":
            return 3;
        case "/":
            return 4;
        default:
            return -1;
        }

    }

    public static boolean opcompare2(String s1, String s2) {
        if (opvalue2(s1) >= opvalue2(s2))
            return true;
        else {
            return false;
        }