简体   繁体   中英

Java: Stacks expression evaluator: empty stack exception when subtracting or multiplying

This is somewhat of a loaded question. I'm writing a Lisp evaluator algorithm which takes input expressions written, for example, as:

(+ (- 6) (* 2 3 4) (/ (+ 3) (*) (- 2 3 1)))

The problem is: it throws an EmptyStack error because of the operations (- 6) and (*)

When I write it as (- 0 6) and (* 1) it compiles correctly with the correct result. I think it is looking to subtract FROM something, and multiply something (can't have no digits). It has something to do with the structure of my operand evaluation. I want to implement this into the program itself, without having to manually add zeros for subtraction and one's for multiplication. Any ideas on how to go doing this?

I've been staring at it all day and can't seem to figure out why my push/pop structure acts up in this way. I tried experimenting with regex, but that did not seem to do the trick.

Program structure:

1) Uses two stacks: expressionStack of type Object, and currentOperationStack of type Double.

2) evaluateCurrentOperation() method evaluates the current operation:

  • Pops operands from expressionStack and pushes them onto currentOperationStack until it finds an operator.

  • Uses the operator on the operands on currentOperationStack.

  • Pushes the result into expressionStack.

3) evaluate() method evaluates the current Lisp expression in inputExpression (using a scanner to read, and case statements for operands) and returns the result.

import java.util.*;

public class LispEvaluator {

/**
 * Input expression
 */
private String inputExp;

/**
 * Stacks created for the main expression and current operation.
 */
private Stack<Object> expStack;
private Stack<Double> currentOpStack;


/**
 * Default constructor initializes input and creates Stack objects.
 */
public LispEvaluator()
{
    inputExp = "";
    expStack = new Stack<Object>();
    currentOpStack = new Stack<Double>();
}

/**
 * Constructor sets inputExpr to inputExpression and creates Stack objects.
 * @param inputExpression
 * @throws LispEvaluatorException
 */
public LispEvaluator(String input_Expression) throws LispEvaluatorException 
{
    // If there is no expression, throws an exception.
    if(input_Expression == null)
    {
        throw new LispEvaluatorException("Input statement is null.");
    }

    // Objects created
    inputExp = input_Expression;
    expStack = new Stack<Object>();
    currentOpStack = new Stack<Double>();
}

/**
 * evaluateCurrentOperation() method evaluates the current operation:
 * - Pops operands from expStack and pushes them onto currentOpStack until it finds an operator.
 * - Uses the operator on the operands on currentOpStack.
 * - Pushes the result into exprStack.
 */
private void evaluateCurrentOperation()
{
    String current_Operation;
    boolean numeric = true;

    // Do... while statement sets current operation (while it is numeric).
    do{
        current_Operation = (String.valueOf(expStack.pop()));

        try{
            Double number = Double.parseDouble(current_Operation);
            currentOpStack.push(number);

        }catch(NumberFormatException nfe){
            numeric = false;
        }
    } while(numeric);


    double result;
    switch (current_Operation) {
        case "*":
            result = currentOpStack.pop();
            while(!currentOpStack.isEmpty()){
                result *= currentOpStack.pop();
            }
            break;
        case "/":
            result = currentOpStack.pop();
            while(!currentOpStack.isEmpty()){
                result /= currentOpStack.pop();
            }
            break;
        case "+":
            result = currentOpStack.pop();
            while(!currentOpStack.isEmpty()){
                result += currentOpStack.pop();
            }
            break;
        case "-":
            result = currentOpStack.pop();
            while(!currentOpStack.isEmpty()){
                result -= currentOpStack.pop();
            }

            break;

        default:
            result = currentOpStack.pop();
            break;
    }

    expStack.push(result);

}

/**
 * evaluate() method evaluates the current Lisp expression in inputExpr and returns the result.
 * @throws LispEvaluatorException 
 */
public double evaluate() throws LispEvaluatorException
{
    Scanner inputExprScanner = new Scanner(inputExp);

    /**
     * Breaks the string into single characters using a delimiter.
     */
    inputExprScanner = inputExprScanner.useDelimiter("\\s*");

    /**
     * Scans the tokens in the string (runs while there is a next token).
     */
    while (inputExprScanner.hasNext())
    {

        /**
         * If it has an operand, pushes operand object onto the exprStack.
         */
        if (inputExprScanner.hasNextInt())
        {
            /**
             * Scanner gets all of the digits (regular expression \\d+ means any digit).
             */
            String dataString = inputExprScanner.findInLine("\\d+");

            expStack.push(Double.parseDouble(dataString));
        }

        else
        {
            /**
             * Gets one character in String token.
             */
            String token = inputExprScanner.next();
            char item = token.charAt(0);

            switch(item)
            {
            // If "(", next token is an operator (so do nothing).
            case '(':
                break;

            // If ")", it will evaluate that expression since parenthesis are closed.
            case ')':                   
                evaluateCurrentOperation();

                break; 

            // If there is an operator "* + / -", then it pushes the operator onto exprStack.
            case'*':
                expStack.push("*");
                break;

            case'+':
                expStack.push("+");
                break; 

            case'/':
                expStack.push("/");
                break;

            case'-':
                expStack.push("-");
                break;  

            /**
             * Default throws an error.
             */
                default:
                    throw new LispEvaluatorException(item + " is not a valid operator");
            } // end switch
        } // end else
    } // end while

    /**
     * If you run out of tokens, the value on the top of exprStack is the result of the expression.
     * 
     */

    return Double.parseDouble(String.valueOf(expStack.pop()));
}

/**
 * Reset method sets inputExpr to inputExpression and clears Stack objects.
 * @param inputExpression
 * @throws LispEvaluatorException
 */
public void reset(String inputExpression) throws LispEvaluatorException 
{
    if(inputExpression == null)
    {
        throw new LispEvaluatorException("Input statement is null");
    }

    inputExp = inputExpression;
    expStack.clear();
    currentOpStack.clear();
}

/**
 * Test method to print the result of the expression evaluator.
 * @param s
 * @param expr
 * @throws LispEvaluatorException
 */
private static void evaluateExprTest(String s, LispEvaluator expr) throws LispEvaluatorException
{
    Double result;
    System.out.println("Expression: " + s);
expr.reset(s);
    result = expr.evaluate();
    System.out.printf("Result: %.2f\n", result);
    System.out.println("-------");
}

/**
 * Main method uses test cases to test the evaluator.
 * @param args
 * @throws LispEvaluatorException
 */
public static void main (String args[]) throws LispEvaluatorException
{
    LispEvaluator expr= new LispEvaluator();

    /**
     * Expressions are tested.
     * Note: For each operation written as (- 6), in order for the Stack to continue,
     * it needs to be written as (- 0 6). This way, it is subtracting from a digit.
     */
    String test1 = "(+ 5 0 10)";
    String test2 = "(+ 5 0 10 (- 7 2))";
    String test3 = "(+ (- 0 6) (* 2 3 4) (/ (+ 3) (* 1) (- 2 3 1)))";
    String test4 = "(+ (- 0 632) (* 21 3 4) (/ (+ 32) (* 1) (- 21 3 1)))";
    String test5 = "(+ 2 6) (* 12 18) (/ (+ 32) (* 1) (- 21 3 1))";
    String test6 = "(+ 1 2 (- 5 1) (*4 11 14))";

    evaluateExprTest(test1, expr);
    evaluateExprTest(test2, expr);
    evaluateExprTest(test3, expr);
    evaluateExprTest(test4, expr);
    evaluateExprTest(test5, expr);
    evaluateExprTest(test6, expr);

}

}

Custom exception class:

public class LispEvaluatorException extends Exception{

    public LispEvaluatorException(String message) {
        super(message);

    }
}

The fixes are pretty straightforward: the crashes that you get are because unary operations are distinct features, so you need to code for them explicitly.

Fixing this problem consists of two parts:

  • Add a check for empty stack prior to the first pop()
  • Add a check for unary minus - and unary division / , which have separate code paths.

Here is the portion of the code that needs fixing:

switch (current_Operation) {
    case "*":
        if (currentOpStack.isEmpty()) {
            result = 1;
            break;
        }
        result = currentOpStack.pop();
        while(!currentOpStack.isEmpty()){
            result *= currentOpStack.pop();
        }
        break;
    case "/":
        if (currentOpStack.isEmpty()) {
            result = 1;
            break;
        }
        result = currentOpStack.pop();
        if (currentOpStack.isEmpty()) {
            // Unary division
            result = 1.0 / result;
            break;
        }
        while(!currentOpStack.isEmpty()){
            result /= currentOpStack.pop();
        }
        break;
    case "+":
        if (currentOpStack.isEmpty()) {
            result = 0;
            break;
        }
        result = currentOpStack.pop();
        while(!currentOpStack.isEmpty()){
            result += currentOpStack.pop();
        }
        break;
    case "-":
        if (currentOpStack.isEmpty()) {
            result = 0;
            break;
        }
        result = currentOpStack.pop();
        if (currentOpStack.isEmpty()) {
            // Unary minus
            result = -result;
            break;
        }
        while(!currentOpStack.isEmpty()){
            result -= currentOpStack.pop();
        }
        break;

    default:
        result = currentOpStack.pop();
        break;
}

Demo.

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