CSE143 Notes for Wednesday, 2/23/08

We continued our discussion of stacks and queues. First we wrote some client code. I said that we would use a queue of int values. So we wrote this line of code to construct one:

        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);
When we run this, we get the following output:

        q = front [] back
The toString method includes the words "front" and "back" to make it clear which end is which for the queue. This became clearer when we added some calls on enqueue:

        Queue<Integer> q = new LinkedQueue<Integer>();
        q.enqueue(13);
        q.enqueue(85);
        System.out.println("q = " + q);
which produces the following output:

        q = front [13, 85] back
Notice that the first value added to the queue is at the front and the second value is at the back. If we were to dequeue, we'd get the first value and if we were to enqueue, the value would go at the back.

I then asked people to help me write some code that would add 10 random values between 0 and 99 to the queue. Someone suggested creating 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 to help me write a method to clear the queue. In other words, I want a method that will remove everything from the queue. At first people suggested writing a while loop that looked at the size, but someone pointed out that it is easier to just use a call on the isEmpty method. We want to loop while it is not empty, so we use Java's negation operator (the exclamation mark) to indicate this:

        public static void clear(Queue<Integer> q) {
            while (!q.isEmpty())
                q.dequeue();
        }
Notice that the parameter is of type Queue<Integer>, using the Queue interface rather than the LinkedQueue implementation.

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())
                sum += q.dequeue();
            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 next = q.dequeue();
                sum += next;
                q.enqueue(next);
            }
            return sum;
        }
By print 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.

We also wrote a new version of clear that would work for a Stack:

        public static void clear(Stack<Integer> s) {
            while (!s.isEmpty())
                s.pop();
        }
In this case, we could use the same method name. Both versions of clear take just one parameter, but the types are different. So if you call clear with a Queue, the first version will be called and if you call it with a Stack, this second version will be called.

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 next = s.pop();
            sum += next;
            s.push(next);
        }
        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, 77] top
        sum = 770
The sum of these numbers is not 770. We're getting that sum because the loop pops the value 77 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:

        int sum = 0;
        Queue<Integer> q = new LinkedQueue<Integer>();
        for (int i = 0; i < s.size(); i++) {
            int next = s.pop();
            sum += next;
            q.enqueue(next);
        }
        while (!q.isEmpty())
            s.push(q.dequeue());
        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 next = s.pop();
            sum += next;
            q.enqueue(next);
        }
        while (!q.isEmpty())
            s.push(q.dequeue());
        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 loops 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 next = s.pop();
                sum += next;
                q.enqueue(next);
            }
            while (!q.isEmpty())
                s.push(q.dequeue());
            while (!s.isEmpty())
                q.enqueue(s.pop());
            while (!q.isEmpty())
                s.push(q.dequeue());
            return sum;
        }
This is the correct version of the method which appears in handout #11.

Below are links to the interface files and implementation files for stacks and queues:


Stuart Reges
Last modified: Fri Apr 25 07:31:26 PDT 2008