简体   繁体   中英

Java Expression Parser & Calculator Shunting Yard Algorithm

So the task is to create our own parser for a expression calculator. For Example:

Input: 3+2*1-6/3 Output: 3

Input: 3++2 Output: Invalid Expression

Input: -5+2 Output: -3

Input: 5--2 Output: 7

The code here solves a part of the problem except that it has a fixed input and negative values cannot be solved, And I'm not quite sure yet if it really does solve the expression with operator precedence. but I already modified it to get an input expression from the user. and I've been wondering for hours how to implement the solving for negative values. help anyone?

NO JAVASCRIPT ENGINE PLEASE.

here's the current code

    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"); }         
        }
    }
}  

UPDATED CODE: Added: unaryToexp() function. what I wanted to do was that everytime a " - " occurs, the code treats it as a binary by changing it to " _ " as another operator and this operator solves multiplies thing by -1 (what I wanted first was to add [-1] and [*] to the rpn stack). still got problems here.

compiler says:

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)* 

I think it has something to do with the Double d1 = Double.valueOf( stack.pop() ); cause it still pops another two values, where I only need one for a solving a unary operator. any help?

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"); }           
        }
    }   
} 

Here you are:

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
    }
}

Take a look at some examples and try to find a rule how to distinguish negative values from operators. A rule like:

 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.

You could iterate through your original token list and create a new token list based on this rules. I have just written such an expression evaluator and have an iterator for tokenizing expressions at hand. plan to publish it after some extensions on GitHub.

EDIT: Here is the iterator, the references and calls should be clear, it is a bit more complex because of support for variables/functions and multi-character operators:

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");
    }

}

Couldn't you use the javascript scripting engine? (you would need a bit of tweaking for the 5--2 expression) The code below outputs:

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

Code:

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");
        }
    }
}

Rather than re-invent the wheel you could use a parser generator such as JavaCC or antlr, which is specifically designed for this kind of task. This is a nice example of a simple expression parser and evaluator in a couple of dozen lines of JavaCC.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM