2012-12-10 54 views
1

所以,我們的任務是爲表達式計算器創建我們自己的解析器。例如:Java表達式解析器和計算器調車場算法

輸入:3 + 2 * 1-6/3 輸出:3

輸入:3 ++ 2 輸出:無效表達

輸入:-5 + 2 輸出: -3

輸入:5--2 輸出:7

這裏的代碼解決了這個問題的一部分,但它有一個固定的輸入負值不能得到解決,而且我不是很確定ÿ如果它真的用運算符優先級解決表達式。 但我已經修改它以從用戶獲得輸入表達式。 我一直在想幾個小時如何實現負值的解決方案。幫助任何人?

NO JAVASCRIPT ENGINE PLEASE。

這裏是當前的代碼

import java.util.*; 

public class ExpressionParser 
{ 
    // Associativity constants for operators 
    private static final int LEFT_ASSOC = 0; 
    private static final int RIGHT_ASSOC = 1; 

    // Operators 
    private static final Map<String, int[]> OPERATORS = new HashMap<String, int[]>(); 
    static 
    { 
     // Map<"token", []{precendence, associativity}> 
     OPERATORS.put("+", new int[] { 0, LEFT_ASSOC }); 
     OPERATORS.put("-", new int[] { 0, LEFT_ASSOC }); 
     OPERATORS.put("*", new int[] { 5, LEFT_ASSOC }); 
     OPERATORS.put("/", new int[] { 5, LEFT_ASSOC });   
    } 

    // Test if token is an operator 
    private static boolean isOperator(String token) 
    { 
     return OPERATORS.containsKey(token); 
    } 

    // Test associativity of operator token 
    private static boolean isAssociative(String token, int type) 
    { 
     if (!isOperator(token)) 
     { 
      throw new IllegalArgumentException("Invalid token: " + token); 
     } 

     if (OPERATORS.get(token)[1] == type) { 
      return true; 
     } 
     return false; 
    } 

    // Compare precedence of operators.  
    private static final int cmpPrecedence(String token1, String token2) 
    { 
     if (!isOperator(token1) || !isOperator(token2)) 
     { 
      throw new IllegalArgumentException("Invalid tokens: " + token1 
        + " " + token2); 
     } 
     return OPERATORS.get(token1)[0] - OPERATORS.get(token2)[0]; 
    } 

    // Convert infix expression format into reverse Polish notation 
    public static String[] expToRPN(String[] inputTokens) 
    { 
     ArrayList<String> out = new ArrayList<String>(); 
     Stack<String> stack = new Stack<String>(); 

     // For each token 
     for (String token : inputTokens) 
     { 
      // If token is an operator 
      if (isOperator(token)) 
      {  
       // While stack not empty AND stack top element 
       // is an operator 
       while (!stack.empty() && isOperator(stack.peek())) 
       {      
        if ((isAssociative(token, LEFT_ASSOC)   && 
         cmpPrecedence(token, stack.peek()) <= 0) || 
         (isAssociative(token, RIGHT_ASSOC)  && 
         cmpPrecedence(token, stack.peek()) < 0)) 
        { 
         out.add(stack.pop());  
         continue; 
        } 
        break; 
       } 
       // Push the new operator on the stack 
       stack.push(token); 
      } 
      // If token is a left bracket '(' 
      else if (token.equals("(")) 
      { 
       stack.push(token); // 
      } 
      // If token is a right bracket ')' 
      else if (token.equals(")")) 
      {     
       while (!stack.empty() && !stack.peek().equals("(")) 
       { 
        out.add(stack.pop()); 
       } 
       stack.pop(); 
      } 
      // If token is a number 
      else 
      { 
      // if(!isOperator(stack.peek())){ 
      //  out.add(String.valueOf(token*10)); 
      //  } 
       out.add(token); 
      } 
     } 
     while (!stack.empty()) 
     { 
      out.add(stack.pop()); 
     } 
     String[] output = new String[out.size()]; 
     return out.toArray(output); 
    } 

    public static double RPNtoDouble(String[] tokens) 
    {   
     Stack<String> stack = new Stack<String>(); 

     // For each token 
     for (String token : tokens) //for each 
     { 
      // If the token is a value push it onto the stack 
      if (!isOperator(token)) 
      { 
       stack.push(token);     
      } 
      else 
      {   
       // Token is an operator: pop top two entries 
       Double d2 = Double.valueOf(stack.pop()); 
       Double d1 = Double.valueOf(stack.pop()); 

       //Get the result 
       Double result = token.compareTo("*") == 0 ? d1 * d2 : 
           token.compareTo("/") == 0 ? d1/d2 : 
           token.compareTo("+") == 0 ? d1 + d2 : 
                  d1 - d2;     
       // Push result onto stack 
       stack.push(String.valueOf(result));             
      }       
     }   

     return Double.valueOf(stack.pop()); 
    } 

    public static void main(String[] args) throws Exception{ 
     Scanner in = new Scanner(System.in); 
     String reg = "((?<=[<=|>=|==|\\+|\\*|\\-|<|>|/|=])|(?=[<=|>=|==|\\+|\\*|\\-|<|>|/|=]))"; 
    while(true){ 
     try{ 
     System.out.println("Enter Your Expression"); 
     //String[] input = "(1 + 2) * (3/4) - (5 + 6)".split(" "); 
     String[] input = in.nextLine() .split(reg); 
     String[] output = expToRPN(input); 

     // Build output RPN string minus the commas 
     System.out.print("Stack: "); 
     for (String token : output) { 
       System.out.print("[ ");System.out.print(token + " "); System.out.print("]"); 
     } 
     System.out.println(" "); 
     // Feed the RPN string to RPNtoDouble to give result 
     Double result = RPNtoDouble(output); 
     System.out.println("Answer= " + result);     
     }catch (NumberFormatException | EmptyStackException nfe){ 
      System.out.println("INVALID EXPRESSION"); }   
     } 
    } 
} 

更新的代碼: 補充:unaryToexp()函數。 我想要做的是每次發生「 - 」時,代碼將其作爲二進制通過將其更改爲「_」作爲另一個運算符來處理,並且該運算符將-1乘以-1(我首先想要添加[ -1]和[*]到rpn堆棧)。這裏仍然有問題。

編譯器說:

Enter Your Expression 
-5+3 
Stack: [ ][ 5 ][ - ][ 3 ][ + ] 
Exception in thread "main" java.lang.NumberFormatException: empty String 
     at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:10 11) 
     at java.lang.Double.valueOf(Double.java:504) 
     at ExpressionParser.RPNtoDouble(ExpressionParser.java:160) 
     at ExpressionParser.main(ExpressionParser.java:194)* 

我覺得它有什麼做的Double d1 = Double.valueOf(stack.pop());原因仍然彈出另外兩個值,在這裏我只需要一個用於求解一元運算符。任何幫助?

public class ExpressionParser 
{ 
    // Associativity constants for operators 
    private static final int LEFT_ASSOC = 0; 
    private static final int RIGHT_ASSOC = 1; 

    // Operators 
    private static final Map<String, int[]> OPERATORS = new HashMap<String, int[]>(); 
    static 
    { 
     // Map<"token", []{precendence, associativity}>  
     OPERATORS.put("-", new int[] { 0, LEFT_ASSOC }); 
     OPERATORS.put("+", new int[] { 0, LEFT_ASSOC }); 
     OPERATORS.put("*", new int[] { 5, LEFT_ASSOC }); 
     OPERATORS.put("/", new int[] { 5, LEFT_ASSOC }); 
     OPERATORS.put("_", new int[] { 5, RIGHT_ASSOC }); 
    } 

    // Test if token is an operator 
    private static boolean isOperator(String token) 
    { 
     return OPERATORS.containsKey(token); 
    } 

    // Test associativity of operator token 
    private static boolean isAssociative(String token, int type) 
    { 
     if (!isOperator(token)) 
     { 
      throw new IllegalArgumentException("Invalid token: " + token); 
     } 

     if (OPERATORS.get(token)[1] == type) { 
      return true; 
     } 

     return false; 
    } 

    // Compare precedence of operators.  
    private static final int cmpPrecedence(String token1, String token2) 
    { 
     if (!isOperator(token1) || !isOperator(token2)) 
     { 
      throw new IllegalArgumentException("Invalid tokens: " + token1 
        + " " + token2); 
     } 
     return OPERATORS.get(token1)[0] - OPERATORS.get(token2)[0]; 
    } 


    // CONVERT UNARY OPERATORS 
    public static String[] unaryToexp(String[] inputTokens) 
    { 
     ArrayList<String> out = new ArrayList<String>(); 
     Stack<String> stack = new Stack<String>(); 
       //if token is an unary minus 
     for (String token : inputTokens) 
     { 
        if(((token == "-") && (isOperator(stack.peek()) || stack.empty() ))){ // 
         token = "_"; 
        } 
        else if (token == "-"){ 
         token = "-"; 
        } 
      out.add(token); 
      while (!stack.empty()) 
       { 
        out.add(stack.pop()); 
       }  
     } 

     String[] output = new String[out.size()]; 
     return out.toArray(output); 
    } 

    // Convert infix expression format into reverse Polish notation 
    public static String[] expToRPN(String[] inputTokens) 
    { 
     ArrayList<String> out = new ArrayList<String>(); 
     Stack<String> stack = new Stack<String>(); 

     // For each token 
     for (String token : inputTokens) 
     { 
      // If token is an operator 
      if (isOperator(token)) 
      { 
       // While stack not empty AND stack top element 
       // is an operator 

       while (!stack.empty() && isOperator(stack.peek())) 
       {      
        if ((isAssociative(token, LEFT_ASSOC)   && 
         cmpPrecedence(token, stack.peek()) <= 0) || 
         (isAssociative(token, RIGHT_ASSOC)  && 
         cmpPrecedence(token, stack.peek()) < 0))  

        { 
         out.add(stack.pop());  
         continue; 
        } 
        break; 
       } 

       // Push the new operator on the stack 
       stack.push(token); 
      } 
      // If token is a left bracket '(' 
      else if (token.equals("(")) 
      { 
       stack.push(token); // 
      } 
      // If token is a right bracket ')' 
      else if (token.equals(")")) 
      {     
       while (!stack.empty() && !stack.peek().equals("(")) 
       { 
        out.add(stack.pop()); 
       } 
       stack.pop(); 
      } 
      // If token is a number 
      else 
      { 
       out.add(token); 
      } 
     } 
     while (!stack.empty()) 
     { 
      out.add(stack.pop()); 
     } 
     String[] output = new String[out.size()]; 
     return out.toArray(output); 
    } 

    public static double RPNtoDouble(String[] tokens) 
{   
    Stack<String> stack = new Stack<String>(); 

    // For each token 
    for (String token : tokens) 
    { 
     // If the token is a value push it onto the stack 
     if (!isOperator(token)) 
     { 
      stack.push(token);     
     } 
     else 
     { 
      // Token is an operator: pop top two entries 
      Double d2 = Double.valueOf(stack.pop()); 
      Double d1 = Double.valueOf(stack.pop()); 

      //Get the result 
      Double result = token.compareTo("_") == 0 ? d2 * -1 : 
          token.compareTo("*") == 0 ? d1 * d2 : 
          token.compareTo("/") == 0 ? d1/d2 : 
          token.compareTo("+") == 0 ? d1 + d2 : 
                 d1 - d2;  

      // Push result onto stack 
      stack.push(String.valueOf(result));             
     }       
    }   
    return Double.valueOf(stack.pop()); 
} 

    public static void main(String[] args) throws Exception{ 
     Scanner in = new Scanner(System.in); 
     String reg = "((?<=[<=|>=|==|\\+|\\*|\\-|\\_|<|>|/|=])|(?=[<=|>=|==|\\+|\\*|\\-|<|>|/|=]))"; 
    while(true){ 
     //try{ 
     System.out.println("Enter Your Expression"); 
     //String[] input = "(1 + 2) * (3/4) - (5 + 6)".split(" "); 
     String[] input = in.nextLine() .split(reg); 
     String[] unary = unaryToexp(input); //.split(reg); 
     String[] output = expToRPN(unary); 

     // Build output RPN string minus the commas 
     System.out.print("Stack: "); 
     for (String token : output) { 
       System.out.print("[ ");System.out.print(token); System.out.print(" ]"); 
     } 
     System.out.println(" "); 
     // Feed the RPN string to RPNtoDouble to give result 
     Double result = RPNtoDouble(output); 
     System.out.println("Answer= " + result);     
     //}catch(){ 
      //System.out.println("INVALID EXPRESSION"); }   
     } 
    } 
} 
+0

「3 ++ 2」可以被認爲是「3 + 0 + 2」,不是嗎? :)。爲什麼'5--2'等於7而不是'3'? '5-2'和'5 - ( - 2)'之間有區別。 –

+0

nope。:)它被認爲是來自用戶的無效輸入。但程序不應該對間距嚴格。喜歡。 3 _____ + 2。已驗證。 :) – binaryjc

+0

因爲這是負值的特殊情況。我不想用積極的價值把它複雜化,因爲輸入被認爲是積極的,除非看到負號。這是否回答你的問題? – binaryjc

回答

1

給你:

private static final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); 

public static String eval(String matlab_expression){ 
    if(matlab_expression == null){ 
     return "NULL"; 
    } 
    String js_parsable_expression = matlab_expression 
      .replaceAll("\\((\\-?\\d+)\\)\\^(\\-?\\d+)", "(Math.pow($1,$2))") 
      .replaceAll("(\\d+)\\^(\\-?\\d+)", "Math.pow($1,$2)"); 
    try{ 
     return engine.eval(js_parsable_expression).toString(); 
    }catch(javax.script.ScriptException e1){ 
     return null; // Invalid Expression 
    } 
} 
+0

'replaceAll'部分是將每個'x^y'轉換爲'Math.pow(x,y)',因爲引擎'eval'函數基於JavaScript語法,其中'x^y'不允許。 –

+0

嗯。我認爲使用eval是邪惡的。儘可能多,我不想在這裏使用任何JavaScript引擎。 :D – binaryjc

+0

感謝您的答案。我試過了。但我仍然不想要eval。 :D – binaryjc

1

看看一些例子,試圖找到一條規則如何從運營商區分負值。 規則,如:

if (token is + or -) and next token is a number 
and 
     (the previous token was empty 
    or the prvious token was ')' or another operator) 
then it is a sign to the current value. 

你可以通過你的原始憑證列表迭代和創建基於此規則的新的令牌列表。 我剛寫過這樣一個表達式求值器,並且有一個用於標記表達式的迭代器。計劃在GitHub上進行一些擴展之後發佈它。

編輯:這裏是迭代器,引用和調用應該是清楚的,那是因爲變量/功能和多字符運營商的支持複雜一點:

private class Tokenizer implements Iterator<String> { 
    private int pos = 0; 
    private String input; 
    private String previousToken; 

    public Tokenizer(String input) { 
     this.input = input; 
    } 

    @Override 
    public boolean hasNext() { 
     return (pos < input.length()); 
    } 

    private char peekNextChar() { 
     if (pos < (input.length() - 1)) { 
      return input.charAt(pos + 1); 
     } else { 
      return 0; 
     } 
    } 

    @Override 
    public String next() { 
     StringBuilder token = new StringBuilder(); 
     if (pos >= input.length()) { 
      return previousToken = null; 
     } 
     char ch = input.charAt(pos); 
     while (Character.isWhitespace(ch) && pos < input.length()) { 
      ch = input.charAt(++pos); 
     } 
     if (Character.isDigit(ch)) { 
      while ((Character.isDigit(ch) || ch == decimalSeparator) 
        && (pos < input.length())) { 
       token.append(input.charAt(pos++)); 
       ch = pos == input.length() ? 0 : input.charAt(pos); 
      } 
     } else if (ch == minusSign 
       && Character.isDigit(peekNextChar()) 
       && ("(".equals(previousToken) || ",".equals(previousToken) 
         || previousToken == null || operators 
          .containsKey(previousToken))) { 
      token.append(minusSign); 
      pos++; 
      token.append(next()); 
     } else if (Character.isLetter(ch)) { 
      while (Character.isLetter(ch) && (pos < input.length())) { 
       token.append(input.charAt(pos++)); 
       ch = pos == input.length() ? 0 : input.charAt(pos); 
      } 
     } else if (ch == '(' || ch == ')' || ch == ',') { 
      token.append(ch); 
      pos++; 
     } else { 
      while (!Character.isLetter(ch) && !Character.isDigit(ch) 
        && !Character.isWhitespace(ch) && ch != '(' 
        && ch != ')' && ch != ',' && (pos < input.length())) { 
       token.append(input.charAt(pos)); 
       pos++; 
       ch = pos == input.length() ? 0 : input.charAt(pos); 
       if (ch == minusSign) { 
        break; 
       } 
      } 
      if (!operators.containsKey(token.toString())) { 
       throw new ExpressionException("Unknown operator '" + token 
         + "' at position " + (pos - token.length() + 1)); 
      } 
     } 
     return previousToken = token.toString(); 
    } 

    @Override 
    public void remove() { 
     throw new ExpressionException("remove() not supported"); 
    } 

} 
+0

我想我應該創建一個函數,如: private static boolean isNegative {}並在那裏應用規則,並調用函數當看到「 - 」時。你認爲這應該工作? – binaryjc

+0

哪裏打電話?我希望在'expToRPN'函數之前調用它,'expToRPN'應該獲得有效標記的列表,以保持分流碼算法的完整。創建另一個函數stringToTokens(),在那裏你可以調用它。 –

+0

oohhh。一旦我能夠編寫代碼,我會盡快回復。但如果你有一些你可以分享的代碼,我會非常感激。 :D – binaryjc

1

無法使用JavaScript腳本引擎?(你將需要爲5--2表達一些調整)下面輸出的代碼:

3+2*1-6/3 = 3.0 
3++2 = Invalid Expression 
-5+2 = -3.0 
5--2 = 7.0 

代碼:

public class Test1 { 

    static ScriptEngine engine; 

    public static void main(String[] args) throws Exception { 
     engine = new ScriptEngineManager().getEngineByName("JavaScript"); 

     printValue("3+2*1-6/3"); 
     printValue("3++2"); 
     printValue("-5+2"); 
     printValue("5--2"); 
    } 

    private static void printValue(String expression) { 
     String adjustedExpression = expression.replaceAll("--", "- -"); 
     try { 
      System.out.println(expression + " = " + engine.eval(adjustedExpression)); 
     } catch (ScriptException e) { 
      System.out.println(expression + " = Invalid Expression"); 
     } 
    } 
} 
1

而不是重新發明可以使用一個解析器生成如JavaCC的車輪或antlr,這是專門爲這種任務而設計的。一個簡單的表達式分析器和評估器在幾十行JavaCC中的一個簡單的表達式(This is a nice example)。