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] back
Then 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] top
Then 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 list afterwards, we found
that the list is empty. As a side effect of calculating the sum, we destroyed
the contents of the list. 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 = 850
The 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] top
There 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: