HW5 - UMass CS !EdLab

advertisement
CmpSci 187 – F09
HW5 Description
Prof. Wendy Lehnert
This assignment will give you an opportunity to simulate recursive method definitions
using an explicit stack in place of the normal calling stack. No actual recursion will take
place in these exercises although you be executing a variety of classic recursive examples.
As you read these instructions, please refer to accompanying .java files that are available
to you on the wiki.
Activation Records for the Calling Stack
If we’re going to simuate the calling stack, we need to design activation records to
represent method calls on the stack. Each activation record needs to record the individual
lines of code in the method body, the actual arguments being passed to the method, and a
pointer to the next line of code to be executed so we can keep track of where we are.
Knowing where to resume the execution of a method is especially important if a nested
method call interrupts the current method call and diverts the flow of control for a while.
We want each (simulated) method definition to follow the same blueprint for an activation
record, so we’ll think of each method as an object whose class extends this abstract
ActivationRecord class:
public abstract class ActivationRecord{
public static Stack<ActivationRecord>
callingStack = new Stack<ActivationRecord>();
public static Integer result;
int currentLine;
ArrayList<Integer> arglist;
abstract void methodBody();
void endProcess(){
if (!callingStack.empty())
callingStack.pop();
}
}
Do not make any changes to the ActivationRecord class.
The Simulated Calling Stack
In order to process activation records on the stack, we have defined a class named
ProcessStack. The ProcessStack method workStack is responsible for executing method
calls (a.k.a. ActivationRecord objects) on the stack one line at a time. The while loop does
this with successive calls to methodBody() for whatever method call is at the top of the
stack. Only one ActivationRecord is active at any given time – the one at the top of the
stack. All other ActivationRecords on the stack are held in suspended animation – waiting
for the flow of control to return to them. The ProcessStack class contains a method named
workStack which is responsible for starting the stack activity and keeping the stack going
until it is empty.
public class ProcessStack {
1
CmpSci 187 – F09
HW5 Description
Prof. Wendy Lehnert
public static Stack<ActivationRecord>
allRecords = ActivationRecord.callingStack;
void workStack(ActivationRecord startingRecord){
int m = 1;
allRecords.push(startingRecord);
while (!allRecords.empty()){
System.out.println("CALLING STACK LOOP " + m++);
allRecords.peek().methodBody();
}
}
}
Note that ProcessStack uses a static Stack object for the actual calling stack, and this is the
same static Stack object that ActivationRecord declared and initialized.
Do NOT remove the println statement inside workStack. We will be looking for those
println outputs when we evaluate your code. Do not make any changes to the
ProcessStack class.
Method Definitions
To see how this really works, let’s define a method that prints out a triangle of stars like
the star patterns you were working with in HW3. We’ll write our triangle method by
creating a Triangle class that extends the abstract ActivationRecord class. We’ve inserted
lots of println statements throughout the Triangle class to show exactly how the action
unfolds. We also use the StarLine class from HW3 (but as a normal method call – not a
simulated one).
Study the Triangle class in Triangle.java and its methodBody to see how the stack moves
through code one line at a time. Then try “executing” the Triangle method like this:
public static void main(String [] args){
ProcessStack ps = new ProcessStack();
ArrayList<Integer> arr = new ArrayList<Integer>(1);
arr.add(5); // create an argument to pass to the method
Triangle tri = new Triangle(arr);
ps.workStack(tri); // execute the tri activation record
}
Note that we use an ArrayList of Integers for the passing int arguments even though we
only need one int for the Triangle class. This ArrayList will be more useful for other
method definitions that require multiple arguments. Run this code and study the trace
sent to the console to better understand how everything works. Note that we execute one
more line of code each time we move through another iteration inside workStack.
The Triangle class methodBody contains 3 case statements prior to case 4 which removes
the current activation record from the stack. Please note that we are using the term “line
2
CmpSci 187 – F09
HW5 Description
Prof. Wendy Lehnert
of code” for each of these case statements rather loosely. For example, lineThree creates
an ArrayList, loads an integer onto the ArrayList, pushes a new ActivationRecord onto the
calling stack, and advances the current line pointer. So this is really a block of code rather
than a single line of code. But conceptually, it’s really just one line of code in the sense that
this is our recursive case where we push another ActiviationRecord onto the stack. In a
“normal” method definition, this would be just one line of code. Here’s what a normal
method definition would look like for the Triangle example:
void triangle(int n){
If (n == 0) ; // exit the method body (lineOne)
else {
System.out.println(new StarLine(0,n)); // (lineTwo)
Triangle(n-1); // (lineThree)
}
}
This is just 3 lines of code: one base case line and two lines inside the else statement.
When you write your own cases for the methodBody method, you can group your code
into blocks like this as well. Try to think about how many lines you’d actually be writing in
a normal method definition and let that guide the way you break things down inside
methodBody.
Exercise 1 (25 pts):
The Triangle class prints triangles that start wide at the top and taper down to nothing at
the bottom. Write a class named Triangle2 that starts with one star at the top and widens
out to n stars at the bottom. Hint: this is almost the exact same class as Triangle – just
change the order of two lines inside the (simulated) method body. If you are confused
about what is needed here, look at the triangle method above and ask yourself how you
would change that code to create a triangle2 method. If you can change the normal
method definition to reverse the order of the printlns, you’ll understand how to change
the methodBody defnition to do the same.
Your solution should contain 3 case statements prior to the one that calls endProcess.
Exercise 2 (25 pts):
The star patterns show how the stack moves through nested method calls but they don’t
compute any return values. To see an example that passes return values from one call to
another, study the Factorial class in Factorial.java. Note how the static Integer variable
result is used to pass the return value of one Factorial method call back to its calling
method. It is important for this variable to be static since it is shared by two
ActivationRecord objects. It should only be used to pass values from one ActivationRecord
to another whenever we pop the stack. Don’t try to use it as a local variable inside any one
ActivationRecord.
3
CmpSci 187 – F09
HW5 Description
Prof. Wendy Lehnert
Also note that the methodBody for Factorial has one case for the recursive call (lineTwo)
and a separate case for the arithmetic associated with the recursive return value
(lineThree). These two statements would be written in a single line of code in a “normal”
method definition, but for our simulation we need to separate them so the arithmetic can
take place in the calling method after the recursive call is popped from the stack. You can’t
collapse these into a single case statement because the recursive call must throw control
to a new ActivationRecord before the artihmetic can be performed.
Write a class named CountMoves that computes the number of moves needed to solve the
Tower of Hanoi for n disks. Compute the answer using the recursive relation #moves(n) =
1 + 2 * #moves(n-1) and model your code on the Factorial class.
Your solution should contain 3 case statements prior to the one that calls endProcess. (Hint:
you only need to change one of the case statements in Factorial).
Exercise 3 (20 pts):
Write a class named FactorialTail that computes factorial values, but unlike Factorial,
FactorialTail uses tail recursion. Pass two arguments on to each of the case statements
instead of just one. Check your trace to make sure all those result values are identical as
the stack is popped. You can accomplish this by making sure the result attribute is set only
once, inside the base case (in lineOne).
Your solution should contain 2 case statements prior to the one that calls endProcess.
Note that Factorial needed a third case statement to cover the arithmetic outside the
recursive call, but your arithmetic will happen when you update your arguments inside
the recursive call. You will therefore need to pass two arguments to lineOne and lineTwo
instead of just one. Your second argument will be used to accumulate partial products as
your computation proceeds. By adding an extra argument, you eliminate a whole case
statement and you achieve tail recursion.
Exercise 4 (10 pts):
To see an iterative solution for the Tower of Hanoi, complete the class named Tower. The
method body for the Tower class has two lines already written and it needs two more. Add
definitions for lineThree and lineFour to complete the Tower class. DO NOT change any of
the existing code in the Tower class.
Note that the four arguments being passed to the case statements consist of the number of
disks we are trying to move (count), and integer names for the 3 pegs (peg1, peg2, peg3).
The first time we call Tower at the top level, peg1 is where all the disks are in the start
state, peg3 is the target peg where we want all the disks to end up in the final state, and
peg2 is just the extra peg. Each time we make a recursive call to solve one of the “meta
steps” for our recursive solution, we need to rearrange the peg arguments to reflect the
4
CmpSci 187 – F09
HW5 Description
Prof. Wendy Lehnert
new start peg, the new target peg, and the new extra peg. Each peg can take on different
roles when we break the problem down into its recursive components. The code for
lineTwo shows how we rearranged the pegs for one of the recursive calls when we loaded
the ArrayList of arguments. Examine a “normal” method definition for the Tower of Hanoi
if you are confused about this.
Exercise 5 (10 pts):
Write a class named DumbFibonacci which computes the terms of the Fibonacci
sequence using the double recursion: Fib(n) = Fib(n-2) + Fib(n-1).
Your solution should contain 4 case statements prior to the one that calls endProcess, and
you should be passing no more than one argument into each case statement.
Note: You will need to add a local variable, an Integer instance attribute to the
DumbFibonacci class so you can save the result of the first recursion while you are waiting
for the second recursion to finish. Notice how many iterations of the while loop inside
workStack are needed to compute the 10th term of the sequence.
Exercise 6 (10 pts):
Write a class named SmartFibonacci which computes the terms of the Fibonacci
sequence using tail recursion.
Your solution should contain 2 case statements prior to the one that calls endProcess, and
you should now be passing three arguments instead of just one.
How many iterations of the while loop inside workStack are needed to compute the 10th
term of the sequence now?
5
Download