Queue<Integer> q = new LinkedQueue<Integer>();Notice that we use the interface for defining the variable. We only use the name of the implementation when we are calling new. All variables, parameters and return types should be defined using the interface, as we discussed on Monday.
Because the implementation includes a toString method, we can print the queue:
System.out.println("q = " + q);I asked people to help me write some code that would add 10 random values between 0 and 99 to the queue. We used a Random object which required us to include an import from java.util:
Queue<Integer> q = new LinkedQueue<Integer>(); Random r = new Random(); for (int i = 0; i < 10; i++) q.enqueue(r.nextInt(100)); System.out.println("q = " + q);which produced output like the following:
q = front [25, 19, 83, 0, 70, 52, 76, 33, 81, 54] backThen we explored how to put this code for generating a queue with random values into a method. I suggested that we have a size parameter indicating how many values to add to the queue. We found that the return type for the method should be Queue<Integer>:
public static Queue<Integer> makeRandomQueue(int size) { Queue<Integer> q = new LinkedQueue<Integer>(); Random r = new Random(); for (int i = 0; i < size; i++) q.enqueue(r.nextInt(100)); return q; }We then changed main to call this method and print the result:
Queue<Integer> q = makeRandomQueue(10); System.out.println("q = " + q);I then asked people how we could clear the queue so that it didn't have anything in it. We don't have an easy way to do it other than to call dequeue repeatedly until the queue is empty. That might end up being very expensive if the queue has a lot of values in it. Another approach is to simply construct a new empty queue:
q = new LinkedQueue<Integer>();I then said that I wanted to work with a stack as well, so I included this line of code:
Stack<Integer> s = new ArrayStack<Integer>();I said that I wanted to write a method that would transfer values from the queue to the stack. We need a loop that will remove values from the queue as long as there are more values left to remove. We can accomplish this with a while loop and the isEmpty and dequeue methods:
public static void queueToStack(Queue<Integer> q, Stack<Integer> s) { while (!q.isEmpty()) { int n = q.dequeue(); ... } }Notice that the parameters are of type Queue<Integer> and Stack<Integer>, using the Queue and Stack interfaces rather than the LinkedQueue and ArrayStack implementations.
This code removed things from the queue, but to add them to the stack, we have to include a call on push inside the loop. At first I showed this incorrect version:
public static void queueToStack(Queue<Integer> q, Stack<Integer> s) { while (!q.isEmpty()) { int n = q.dequeue(); s.push(q.dequeue()); } }The problem with this version is that it calls dequeue twice each time through the loop. It should be calling push with the value of n. Someone pointed out that we could eliminate the line that involves n altogether and have just the second line of code, which is true. But I changed it to work with n:
public static void queueToStack(Queue<Integer> q, Stack<Integer> s) { while (!q.isEmpty()) { int n = q.dequeue(); s.push(n); } }I added some code to main to report what was in the stack and queue after calling queueToStack:
Queue<Integer> q = makeRandomQueue(10); System.out.println("q= " + q); Stack<Integer> s = new ArrayStack<Integer>(); queueToStack(q, s); System.out.println("after queueToStack:"); System.out.println(" queue = " + q); System.out.println(" stack = " + s);It produced output like the following:
q = front [75, 76, 53, 82, 88, 77, 63, 28, 86, 7] back after queueToStack: queue = front [] back stack = bottom [75, 76, 53, 82, 88, 77, 63, 28, 86, 7] topThen I asked people how we could write a method that would find the sum of the values in this queue. It is a cumulative sum task, which involves initializing a sum variable to 0 outside the loop and then adding each value to the sum as we progress through the loop. Our first attempt looked like this:
public static int sum(Queue<Integer> q) { int sum = 0; while (!q.isEmpty()) { int n = q.dequeue(); sum += n; } return sum; }When we called the method from main and printed the queue afterwards, we found that the queue is empty. As a side effect of calculating the sum, we destroyed the contents of the queue. This is generally not acceptable behavior.
Unfortunately, queues don't give us any peeking operations. We have no choice but to take things out of the queue. But we can restore the queue to its original form. How? Someone suggested using a second queue. That would work, but there is an easier way. Why not use the queue itself? As we dequeue values to be processed, we re-enqueue them at the end of the list. Of course, then the queue never becomes empty. So instead of a while loop looking for an empty queue, we wrote a for loop using the size of the queue:
public static int sum(Queue<Integer> q) { int sum = 0; for (int i = 0; i < q.size(); i++) { int n = q.dequeue(); sum += n; q.enqueue(n); } return sum; }By printing the queue before and after a call on sum, we were able to verify that our new version preserved the queue.
Then I created a variation of makeRandomQueue that I called makeRandomStack for making a stack of random values. This was a fairly straightforward modification where we simply switched queue operations with stack operations:
public static Stack<Integer> makeRandomStack(int size) { Random r = new Random(); Stack<Integer> s = new ArrayStack<Integer>(); for (int i = 0; i < size; i++) s.push(r.nextInt(100)); return s; }In this case I had to use a different name because otherwise the signatures would match (both with a single parameter of type int). In Java, the return type is not part of the signature.
In place of queueToStack, we wrote a new method stackToQueue:
public static void stackToQueue(Stack<Integer> s, Queue<Integer> q) { while (!s.isEmpty()) { int n = s.pop(); q.enqueue(n); } }I then said I wanted to consider a variation of the sum method for stacks:
public static int sum(Stack<Integer> s) { ... }This method can also be called sum because the two methods have different signatures. Remember that a signature of a method is its name plus its parameters. These are both called sum and they both have just a single parameter, but the parameter types are different, so this is okay. This is called overloading a method and it is a common technique in Java.
So how do we write the sum method for stacks? At first I did a similar modification where I simply substituted stack operations for queue operations:
int sum = 0; for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; s.push(n); } return sum;Unfortunately, this code didn't work. We saw output like this when we tested it from main:
stack = bottom [42, 19, 78, 87, 14, 41, 57, 25, 96, 85] top sum = 850The sum of these numbers is not 850. We're getting that sum because the loop pops the value 85 off the stack 10 different times and then pushes it back onto the top of the stack. With a queue, values go in at one end and come out the other end. But with a stack, all the action is at one end of the structure (the top). So this approach isn't going to work.
In fact, you can't solve this in a simple way with just a stack. You'd need something extra like an auxiliary structure. I said to consider how we could solve it if we had a queue available as auxiliary storage. Then we can put things into the queue as we take them out of the stack and after we have computed the sum, we can transfer things from the queue back to the stack using our queueToStack method:
int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q, s); return sum;This also didn't work. Here is a sample execution:
initial stack = bottom [32, 15, 54, 91, 47, 45, 88, 89, 13, 0] top sum = 235 after sum stack = bottom [32, 15, 54, 91, 47, 0, 13, 89, 88, 45] topThere are two problems here. Only half of the values were removed from the stack and those values now appear in reverse order. Why only half? We are using a for loop that compares a variable i against the size of the stack. The variable i is going up by one while the size is going down by one every time. The result is that halfway through the process, i is large enough relative to size to stop the loop. This is a case where we want a while loop instead of a for loop:
int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while (!s.isEmpty()) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q, s); return sum;Even this is not correct. It finds the right sum, but it ends up reversing the values in the stack. Some people said, "Then why don't you use a stack for auxiliary storage?" That would solve the problem, but one of the things we are testing is whether you can figure out how to solve a problem like this given a certain set of tools. In this case, you are given auxiliary storage in the form of a queue.
The problem is that by transferring the data from the stack into the queue and then back into the stack, we have reversed the order. The fix is to do it again so that it goes back to the original. So we add two extra calls at the end of the method that move values from the stack back into the queue and then from the queue back into the stack:
public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while (!s.isEmpty()) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q, s); stackToQueue(s, q); queueToStack(q, s); return sum; }This is the correct version of the method which appears in handout #8.
Below are links to the interface files and implementation files for stacks and queues: