CS210 Thurs., Mar. 2

Note: Midterm March 28, hw3 due next Thurs., pa2 deadline noon, Saturday.

ReverseLines Example done with a Stack

as at end of last class

What’s in the JDK?  There is a Stack class (not an interface), extending the Vector class, with a slightly different API.  Weiss uses the JDK Stack class in the stack apps in Chap. 11.

The JDK Stack class has the version of pop() that returns the old top element: Object pop();  It uses “peek()” for accessing the top element instead of top().

The JDK Stack implements Collection and List, so that means you can get an iterator and access all the objects in the stack.  Not a pure stack at all.  Most apps would stick to the classic stack operations, but there’s possibility of other actions.  Thus our mental model is fuzzed up until we check the code for non-stack-ADT actions.

We can define a "PureStack" subset of the JDK API as follows:

push, pop, peek,  isEmpty, clear.  (also toString for debugging)

See pg. 558 for an implementation, drop size().

Thinking in Stacks.

Question: given a PureStack, is it possible to determine the number of elements in the stack without emptying it?

Answer: no.

We can determine whether the stack has anything in it, but not how many things are in it, if we are not allowed to empty it.

If we are allowed to empty it, we could use a loop of pops until isEmpty is true, counting as we go, to determine how many elements were in it.

If we had a second (empty) stack, we could pop things out of the first stack and push them into the second until the first was empty, counting as we go.  Then we could pop the elements out of the second stack and push them back into the first.  That way, we would end up with the same stack contents in the first stack that we started with, and also the same empty second stack.

Question: given a PureStack with at least two elements, is it possible to display the element just under the top and leave the stack the same as you found it?

Answer: Yes, pop off the top into one element variable, and then display the new top.  Then push the old top back on.

Parentheses Checking: discussed in Weiss, pg. 226

Here we want to check for balanced parentheses, such as (())  and (()()) and with <> and {} similarly, so that ({<><()>}()) is considered balanced because all pairs of parentheses are properly nested.  Each closing parenthesis, ) or > or }, can be matched up with a corresponding opening parenthesis, ( or < or {, with only balanced parentheses between them, if anything.

To do this we can use a stack to save symbols that are not yet matched as we work our way from left to right.  See the pseudocode on pg. 226.

Example "( < > )" 

read (, push (

read <, push <

read >, stack not empty, pop <, matches, OK

read ), stack not empty, pop (, matches, OK

read EOF, stack empty, OK, done


Now consider coding this using the JDK Stack or PureStack..

We can’t have a Stack of char, need Stack of Object.

No problem, each char can be turned into a Character object, just as ints relate to Integer.  And we can use autoboxing

So a stack of Character does the trick—see example code for CheckParentheses.java.

 
Expression Evaluation and Intro to pa3

We've been looking at Stacks and simple stack apps.  pa3 covers a somewhat more ambitious stack app, an infix calculator. 

For example, this calculator can calculate the value arithmetic expression (1 + 2*3)*4 and find that it is 28

You will start from Weiss’s Evaluator.java available at his textbook website and displayed and discussed in Chap. 11.  It can already do the above expression. You’ll refactor the classes to separate out the top-level code from the handling of the tokens, and add two more operators.

First it is essential to build up a mental model of how the calculator works, and that’s what we’ll cover today.  Next time we’ll consider coding considerations.

Note that we will follow Weiss in adding an exponentiation operator ^, so 2^3 = 8.  In C and Java. ^ is exclusive or and there is no exponentiation operator.

Other than the use of ^, the expressions we will use will follow the rules of C and Java (which agree on these rules.)

Idea of Precedence in Expression Evaluation

We all know from math that to calculate  1 + 2*3, we multiply 2*3 and then add 1.  We do not add 1 + 2 and then multiply by 3, another simple interpretation of this expression.   What we are doing is following a precedence rule: multiplication has higher precedence than addition, so we group the numbers around the multiplication and do that operation first, then the addition.

We can express this by adding parentheses:  1 + 2*3 = 1 + (2*3).  Precedence rules can be thought to provide implicit parentheses like this.

Similarly, division has higher precedence than addition, so 2 + ¾ = 2 + (3/4) = 2 + 1 = 3 using integer arithmetic.

The precedence table from pa3, agreeing with Java and C except for ^, is:

 

Very high:

^

High:

*, /, %

Medium:

+, -

Low:

&

Lower:

|

 

Here & is bitwise AND and | is bitwise OR.

For |, bitwise OR, a bit in ON either operand makes the bit ON in the result.

Ex: 6 = binary 0110, 5 = binary 0101, and 6|5 can be calculated by putting the bits next to each other vertically like this:

6 = 0110

5 = 0101

      -----

      0111 = 7.  Thus 6|5 = 7.

For &, bitwise AND, a bit needs to be ON in both operands to make the bit ON in the result.

Ex: 6 = 6&5 can be calculated by putting the bits next to each other vertically like this:

6 = 0110

5 = 0101

      -----

      0100 = 4.  Thus 6&5 = 4.

What about precedence? 

We see from the table that & has higher precedence than |.  Thus 6|5&3 = 6|(5&3) = 6|1 = 7.

We also see that & and | have lower precedence than +.

Ex: 6+4&3 = (6 + 4) & 3 = 10&3 = 2.

Exponentiation ex.:  2*3^2 = 2*(3^2) = 2*9 = 18

If we have a complicated expression, like 2|4 + 2&7*10^2, we can tackle it by looking for the highest-precedence, adding parens for it:

2|4+2&7*(10^2)       Then looking for the next highest precedence, and so on.

2|4+2&(7*(10^2))

2|(4+2)&(7*(10^2))

2|((4+2)&(7*(10^2)))

(2|((4+2)&(7*(10^2))))

 OK, we have seen how to do any expression with one operator at each precedence level.  What if we have more than one operator at a certain level?

Ex. 2+3+4.  Here it doesn’t matter how we parenthesize, since (2+3) + 4 = 2 + (3+4).  In fact, C/Java use the former, (2+3) + 4, called left-to-right associativity.

Ex: 24/6/3.  Now it matters.  This is also done left-to-right, as (24/6)/3.

In fact, all the operators in the pa3 table use left-to-right associativity except exponentiation, which uses right-to-left:

2^3^2 = 2^(3^2) = 2^9 = 512

Don’t forget % is modulus, remainder after division, same precedence as *, so 2*3%4 = (2*3)%4 = 6%4 = 2 by left-to-right associativity.

Evaluating expressions using a stack: postfix calculator

Following Weiss’s presentation in Chap. 11, we first consider a “postfix calculator” or “reverse Polish calculator”.  To add two numbers with a postfix calculator, you enter “2 3 +“.  To multiply them, enter “2 3 *”.  To multiply 2 and 3 and add 1 to the result, you can enter “1 2 3 * + “ or  “2 3 * 1 +”.  Either way, the substring “2 3 *” is specifying one operand for the addition.

We see that the sub-calculations are nested in the full string.  When we see nesting, we think of stacks. 

Read 11.2.1 on Postfix Machines.

Here is the simple example “1 2 3 * +” showing the stack at each symbol processing.  We’ll write the stacks horizontally, growing to the right.

read 1:  stack = [1]

read 2:  stack = [1, 2]

read 3: stack = [1, 2, 3]

read *: pop 3 as rhs and 2 as lhs, calc 2*3 push 6, stack = [1, 6]

read +: pop 6 as rhs and 1 as lhs, calc 1+6 = 7. push 7, stack = [7]

You can skip 11.2.2 at this point—we didn’t cover it in class.  Maybe we can get back to it.  We went on to consider infix calculators.

Infix calculators: expression evaluation like C/Java code

To add two numbers with an infix calculator, you enter “2 + 3”, to multiply them, “2*3”.  To multiply 2 and 3 and add 1 to the result, “1 + 2*3” or “2*3 + 1”.

Pretty easy for the human.  Not as easy for the calculator device. 

The algorithm to process the input symbol by symbol is quite a bit harder, and needs two stacks, the valStack and the opStack.

First we consider expressions that don’t need associativity rules. For example, they have only one operator from each level.

    1 + 2 * 3:

read 1: push 1 on postfixStack              postfixStack: [1]   opStack: []

read +: push + on opStack                 postfixStack: [1] opStack: [+]

read 2: push on postfixStack

postfixStack: [1, 2] opStack: [+]

read *: there is an operator at top of opStack but it’s of lower precedence, so skip it. push * on opStack

postfixStack: [1, 2] opStack: [+, *]

read 3, push on postfixStack

postfixStack: [1, 2, 3] opStack: [+, *]

read EOS, eval top until opStack is empty:  pop *, pop 3 as rhs, 2 as lhs, calc 2*3, push on stack: postfixStack: [1, 6] opStack: [+]

            pop +, pop 6 as rhs and 1 as lhs, 1+6 = 7, push 7.  Done

postfixStack: [7] opStack: []