Thursday, Mar. 9

Look at Evaluator.java, starting on pg. 407..  Note how this page has a summary of the whole class, with the details on the following pages.

 

How do we tackle reading a class for the first time?

--read the header comments

--read the signatures of the public methods, to see what services this class offers, and what objects can be made.

Note the inner classes in use. I don't expect you to be knowledgable about inner classes yet.  In pa03, the first thing to do is to pull these classes out to be ordinary public classes without rewriting large parts of them. 

Here we see a constructor that takes a string and creates an Evaluator object for it.

Then we call getValue() to do the evaluation and return the integer result.

 

Look at the main method on pg. 414, see the expected construction of an Evaluator object ev, given the string to evaluate such as “1+2*3”.  Then ev.getValue() to do the evaluation, and the result is printed out.

 

Note: keep this driver code as main for ExprEvaluator, just changing the class name as needed.

 

We can run Evaluator as follows:

 

java Evaluator

Enter…

1+2*3

Read 1+2*3

7

Enter…

2^3^2

Read 2^3^2

512

 

Using an input file by redirection:  Create file “infile1” say with contents

1+2*3

2^3^2

 

and run like this:

 

java Evaluator < infile1

Enter…

Read 1+2*3

7

Enter…

Read 2^3^2

512

 

or redirect input and output:

java Evaluator < infile1 > outfile1

 

Note: these work equally well on Windows and UNIX, and this can be done from inside Dr. Java too, in the Interactions  box, once the  working directory  is set right.

 

Back to pg. 407.  We see 3 private static classes dealing with tokens.  These are to be moved out of Evaluator into ordinary public classes. It is clear after studying the whole code that Precedence is just a simple helper class that should be hidden away inside Evaluator, so you can leave it there if you want.

 

Also note the constructor implementation with the string of operator symbols (line 28.)  That list of symbols should not be here in the top-level code, but rather inside the token handling code.  You are going to change line 36 to private String inString; say, and create a Scanner instead of a  StringTokenizer in the tokenizing class.

 

We see the two stacks defined on lines 34-35. 

 

Now let’s look at the precedence decision we worked so hard on last class.  See pg. 413

 

            while( precTable[ lastType ].inputSymbol <=

                   precTable[ topOp = opStack.peek() ].topOfStack )

                binaryOp( topOp );   // evaluate top of opStack

 

What a mess!  What’s going on here? 

lastType is LPAREN or PLUS or MULT or EXP or EOL or…, the input symbol (the one being processed)

topOp is RPAREN or PLUS or MULT or EXP or EOL or …, the symbol at top of stack

precTable[ lastType ] looks up the Precedence object for the input symbol, for example (1, 2) for PLUS

precTable[ lastType ].inputSymbol is the score for this type of symbol as an input symbol (1 for PLUS)

and similarly precTable[topOp].topOfStack is the score for this type of symbol as top-of-stack symbol (2 for PLUS)

 

Basically this comparison is     score_of_lastType  <= score_of_topOp  

                    or                       input_op_score <= top_op_score         as we discussed last time.

 

Examples:   Consider a top_op of  *, with score of 4, and various ops coming in:

top_op = *, in_op = +, scores are 1 vs. 4, do eval-top  (case of unequal precedence: + is weaker than * no matter what)

top_op = * and in_op = ^, scores are 6 vs. 4, don’t do eval-top (case of unequal precedence: ^ is stronger than * no matter what)

top_op = * and in_op = *, scores are 3 vs 4, do eval-top (case of equal precedence, left-to-right assoc op)

Similarly

top_op = ^ and in_op = ^, scores are 6 vs 5, don’t do eval-top (case of equal precedence, right-to-left assoc op)

 Aside: it would be nice to ask the Token objects for their scores.  That would be more object-oriented.  However, we don’t have a Token object for the symbol at top of stack, because the stack only holds integers representing the types of the symbols, such as MULT.  The Token objects have relatively short lifetimes, since they fall out of use when the symbol is processed by processToken.  If we changed the Stacks to hold Tokens instead of just types, and enhanced Tokens, we could ask the Tokens for their scores.  But that is a bigger change in the program than I was planning.  It also uses more memory.

Object Lifetimes

Objects are created by new (or Class.newInstance) and are guaranteed to live until they are no longer referenced by any program reference variables, directly or indirectly.  See pg. 31.  In this program, Tokens are created for each input Token, but the Stacks only have Integers on them, so there is no mechanism for storage of a collecton of Tokens.  Thus the Tokens become unreferenced, subject to garbage collection.

How many objects does it take to do 2+3?                                             Number of Objects in use at this point

( not counting original i/o objects in main).
p. 414, main: one String to hold "2+3", 1 Evaluator, constructor, pg. 407, creates 2 Stack   4

calls getValue, pg. 410, 1 EvalTokenizer, which wraps one StringTokenizer/Scanner            6

  calls tok.getToken(), pg 409: new Token(VALUE, 2)                                                       7

   calls processToken, pg. 413, push new Integer (autoboxing) (old Token is no longer in use)    7

   call tok.getToken(), new Token(SUM)                                                                             8  

   calls processToken, push new Integer  (old Token is no longer in use)                                8

   calls tok.getToken(), new Token(VALUE, 4)                                                                    9

   calls processToken() push new Integer (old Token is no longer in use)                                9

   calls tok.getToken(), new Token(EOL)                                                                            10

    calls processToken() 

          calls binaryOp, pop 3 old Integers, push new Integer                                                   8

           old Token is no longer in use                                                                                      7

return to getValue: pop Integer, return, freeing EvalTokenizer (and its Scanner) and Integer    4

return to main, next calc. overwrites str and ev, freeing old Evaluator and its Stacks.               0


Another example of generrating lots of little objects: adding characters to a String, one by one.  Better to use StringBuffer.  See qual solution for an example.
Since object creation is a non-trivial operation, we can see why a calculator written in C would run much faster than this Java program.  The ease of creating objects in Java can also be its downfall for high performance computing.  But a programmer who knows what's going on can keep things under control.

 

We are now done with the coverage of Stacks.

 

Recall that Stacks and Queues are both specialized lists.

 

Queues—pg. 227, a “pure” Queue.  Put elements in one end and never get to access them until they come out the other end.

Generic queue operations (no API, just ideas)

enqueue--insertion of item at back of queue

dequeue--removal of item from front of queue

getFront--access item at front of queue

 

Queues are use for dataflow and “workflow”, where tasks flow from one person to another.  In cases like this, different programs or different parts of the same program are doing the enqueue and dequeue operations.  The queue can save objects that are in transit and allow pacing flexibility at both ends.

What API do we use in Java?  All the JDK has is a Queue interface extended from Collection, and implemented by LinkedList.

Part of the Queue interface is shown on pg. 229:
T element()--access item at front of queue
T remove()--remove item at front of queue, or throw if queue empty  (not to be confused with Collection boolean.remove(Object o))

 from Collection:

void add(T)--for LinkedList, appends new item at end of list

So Queue implemented by LinkedList and after add of A, B, C  looks like this

    A  -   B  -   C

front              back

remove will give us back A, the first one in.


 

Almost done with Chap. 6.  .