// APS105S // Calculator Example Program. // // This program uses stacks and binary trees to read a // "normal-looking" equation from the user, store it internally, // and compute an answer (in double format). // // The elements of an equation must be comprised as follows: // - numbers will be treated as double values // - double operators: * / + - and ^ (exponent/power) // - parentheses: ( and ) // // Additional rules: // - no variables like "x" // - there must be a space between every element, examples: // 1 + 2 // 1 + ( 2 - 3 ) * 4 // 1 + 2 * 3.001 ^ 4.2 // 1 + 2 * 3.001 ^ 4.2 // - the equation must fit on one line // - parentheses must be properly matched; don't forget any! // - order of operations is respected -- use ( ) if you need to // - same-priority operators are evaluated left-to-right // // Enter an empty equation to quit. // // This program does not check for 100% valid input. Some input // appears valid to it, but is incorrect. It does not gracefully // handle errors when parentheses are mismatched. // // Errors that are unrecognized/ignored: // 1 2 + 3 <- doesn't notice error, ignores the '1' // 1 + 2 * 3 ) <- doesn't notice error // 1 + ( 2 * 3 <- prints error, tries its best. // // The expression is read in using the Shunting Yard Algorithm. // For details, please see the accompanying lecture notes. class Elem { String val; // value: contains either an integer (leaf nodes) // or an operator (branch nodes) Elem next; // for the stack: linked-list next pointers Elem left; // for binary tree: left and right pointers Elem right; // public Elem( String s ) { val = s; } // methods to "walk the tree" for us // there are 3 ways to walk the tree: pre-order, in-order, and post-order // these 3 methods generate: // a prefix equation, // an infix equation (what you're normally used to), and // a postfix equation (reverse polish notation) // we'll use commas to separate numbers in the prefix/postfix form. // we'll add parentheses in the infix form to preserve order of operations public String toPrefixString() { String str = val; if( left != null ) str = str + "," + left.toPrefixString(); if( right != null ) str = str + "," + right.toPrefixString(); return str; } public String toInfixString() { String str = ""; if( left != null ) str = "(" + left.toInfixString(); str = str + val; if( right != null ) str = str + right.toInfixString() + ")"; return str; } public String toPostfixString() { String str = ""; if( left != null ) str = left.toPostfixString() + ","; if( right != null ) str = str + right.toPostfixString() + ","; str = str + val; return str; } // val is an operator. compute (x val y), // and return the answer. public double operate( double x, double y ) { if( val.equals("^") ) return Math.pow( x, y ); else if( val.equals("*") ) return x * y; else if( val.equals("/") ) return x / y; else if( val.equals("+") ) return x + y; else if( val.equals("-") ) return x - y; else { System.out.println("Error, unknown operator: " + val); return 0.0; } } // walk the tree, evaluating the equation. // this is one in post-order, because the left and right // side values must be known to operate on them. public double eval() { double answer; if( left == null && right == null ) { // no children, must be a number // convert 'val' to a double try { answer = Double.valueOf(val).doubleValue(); } catch( NumberFormatException e ) { answer = 0.0; } return answer; } else { // has children, must be an operator double leftValue = left.eval(); double rightValue = right.eval(); answer = operate( leftValue, rightValue ); return answer; } } } // End of "class Elem" // You've all seen stack code before right? // This implementation is based on linked lists. // Well keep track of the top of the stack with a // topPtr reference. class Stack { Elem topPtr; public Stack() { topPtr = null; } public void push( Elem e ) { e.next = topPtr; topPtr = e; } public Elem pop() { Elem e = topPtr; if( topPtr != null ) topPtr = topPtr.next; return e; } public Elem peek() { return topPtr; } public boolean isEmpty() { return topPtr == null; } } // End of "class Stack" public class Calc { public static Stack oprStack; public static Stack numStack; // give the priority of the operator. higher number = higher priority, // which is more tightly binding. public static int priority( String opr ) { if( opr.equals("^") ) return 3; else if( opr.equals("*") || opr.equals("/") ) return 2; else if( opr.equals("+") || opr.equals("-") ) return 1; else // don't know what it is, lowest binding priority return 0; } // break up the equation into two pieces: the first element (word) // and the "rest" of the equation after the first word is removed. // we'll assume spaces separate *all* of the terms and operators in // an equation public static Elem getFirstElem( String equation ) { int i = equation.indexOf(" "); String firstPart; if( i > 0 ) firstPart = equation.substring(0,i+1).trim(); else firstPart = equation; return new Elem( firstPart ); } public static String removeFirstWord( String equation ) { int i = equation.indexOf(" "); if( i > 0 ) return equation.substring(i+1).trim(); else return ""; } // take the top operator off the opr stack // take its two arguments off the number stack // bind them together into a tree // save the tree back on the number stack public static void bindTopOpr() { if( !oprStack.isEmpty() ) { Elem subExpr = oprStack.pop(); subExpr.right = numStack.pop(); subExpr.left = numStack.pop(); numStack.push( subExpr ); } } // we previously stacked a "(" on oprStack, and // we just saw a ")" in the equation input. // bind everything from opening "(" to matching ")" in a tree, // and then put this tree back on the numStack // return early if there is an error. public static void doBrackets() { while( true ) { Elem opr = oprStack.peek(); if( opr == null ) { // oops, we ran out of equation terms and didn't find matching "(" System.out.println( "Error, missing ("); return; // there was an error, quit early } else if( opr.val.equals("(") ) { // remove "(" from oprStack, its no longer needed oprStack.pop(); return; // there was no error, normal quit } else { // collapse top 2 subtrees on numStack and top operator // places resulting tree back on numStack bindTopOpr(); } } } // store the new operator 'opr' on the stack because we haven't seen its // right-hand argument yet. before we do that however, we must check if // the previous operator was higher priority; if so, it must bind to its // two arguments now. public static void pushNewOpr( Elem opr ) { Elem prevOpr = oprStack.peek(); while( prevOpr != null && priority(prevOpr.val) >= priority(opr.val) ) { // if prev opr was higher priority than this one, // collapse the previous expression into a tree bindTopOpr(); prevOpr = oprStack.peek(); } // now push our current operator on the oprStack oprStack.push( opr ); } // parse equation, one term at a time. (read comment at the top of this // file to see what the input looks like). the terms get pushed on stacks // and "binding" is done when we can collapse a subexpression down into a // tree. public static void parseEquation( String equation ) { // start equation with an extra "(" oprStack.push( new Elem("(") ); while( ! equation.equals( "" ) ) { Elem firstPart = getFirstElem( equation ); equation = removeFirstWord( equation ); if( firstPart.val.equals(")") ) { // pop everying until the previous ( on the stack doBrackets(); } else if( priority(firstPart.val) > 0 ) { // one of: ^ * / + - pushNewOpr( firstPart ); } else if( firstPart.val.equals("(") ) { // don't evaluate prev opr yet, wait till ) is seen oprStack.push( firstPart ); } else { /* have a number */ numStack.push( firstPart ); } } // end equation with an extra ")" // this automatically does final cleanup for us :-) doBrackets(); } // print the equation, as stored in the tree, 3 different ways. // evaluate the equation and print the answer. public static void printResult() { Elem eqn = numStack.pop(); System.out.println( "Printing infix version:" ); System.out.println( "\t" + eqn.toInfixString() ); System.out.println( "Printing prefix version:" ); System.out.println( "\t" + eqn.toPrefixString() ); System.out.println( "Printing postfix version:" ); System.out.println( "\t" + eqn.toPostfixString() ); System.out.println( "Evaluating expression:" ); System.out.println( "\t" + eqn.eval() ); // sometimes errors leave things on the stacks, // so this forces the stacks to be emptied. while( ! numStack.isEmpty() ) numStack.pop(); while( ! oprStack.isEmpty() ) oprStack.pop(); } // repeatedly ask for a new equation, evaluate it, print answer. // stop when the equation input is empty. public static void main( String[] args ) { String input; String output; numStack = new Stack(); oprStack = new Stack(); while( true ) { // ask the user for an equation System.out.print("\nEnter equation:\n\t"); input = Stdin.getString(); input = input.trim(); // remove whitespace before and after if( input.equals("") ) break; parseEquation( input ); printResult(); } System.out.println("Done."); } } // End of "class Calc"