20: Building a Stack

Announcements

Quiz in our next discussion (Tuesday!).

Start thinking hard about whether you should enroll in 187 next semester. Are you hanging on by your fingernails in this class? Maybe have a fallback plan in case computer science is not for you.

Implementing a stack

In the last few lectures, we mentioned specialized linear data structures. One of them is the stack, a last-in, first-out data structure. How might we go about building one? Today we’ll build two implementations of a stack in class, based upon the ADT specified by a Java interface.

As we go through this, see how well you can follow along, and see if the in-class exercise seems easy or hard. This is the first of several “barometer” tasks you’ll be doing: if you find them easy (or at least, doable), then you should feel reasonable optimistic about 187 next semester. If you find them difficult or confusing, again, maybe have a fallback plan (or plan to study hard over the summer, perhaps practicing on 190D assignments you had trouble with).

The interface

package stack;

public interface Stack<E> {
    void push(E e) throws StackOverflowException;
    E pop() throws StackUnderflowException;
    E peek() throws StackUnderflowException;
    boolean isEmpty();
    boolean isFull();
    int size();
}

This is a minimal interface; of course you can add more if you like. Note we include thrown exceptions, which is optional depending upon whether the exceptions are “checked” or “unchecked”; the former inherit from Exception and the latter from RuntimeException. See https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html for more details.

We’ll trivially define (empty) classes for each exception ourselves. (in Eclipse)

Array-based stack

Now let’s look at an array-based implementation of a stack. We’ll do a “bounded” stack, that is, a stack that has a fixed maximum capacity. (You can imagine an unbounded, growable array, like the one we talked about for lists earlier in the semester, but we’ll skip that for now.)

What other state, that is, instance variables, does the stack require, other than an array? A pointer into that array, that indicates where the top of the stack “lives”. You have some discretion here – it could index the current top of the stack, or it could index the spot where the next top will be. This is a stylistic choice, but I find pointing it to the current top (or -1, if the stack is empty) to be more natural. Up to you.

(on board)

package stack;

public class BoundedArrayStack<E> implements Stack<E> {

    private E[] array;
    private int top;

    public BoundedArrayStack(int capacity) {
        array = (E[]) new Object[capacity];
        top = -1;
    }

    @Override
    public void push(E e) throws StackOverflowException {
        if (isFull()) {
            throw new StackOverflowException();
        }
        top++;
        array[top] = e;
    }

    @Override
    public E pop() throws StackUnderflowException {
        if (isEmpty()) {
            throw new StackUnderflowException();
        }
        E item = array[top];
        array[top] = null;
        top--;
        return item;
    }

    @Override
    public E peek() throws StackUnderflowException {
        if (isEmpty()) {
            throw new StackUnderflowException();
        }
        return array[top];
    }

    @Override
    public boolean isEmpty() {
        return top == -1;
    }

    @Override
    public boolean isFull() {
        return top == array.length - 1;
    }

    @Override
    public int size() {
        return top + 1;
    }

}

Note 1: You can’t instantiate generic, typesafe arrays in Java, for historical and technical reasons that are beyond the scope of the course. Note the workaround: array = (E[]) new Object[capacity]; works, but generates a warning. As long as no references to the array escape the enclosing object (that is, as long as we never return the E[] array to a caller) we’ll be OK.

Note 2: When we pop, we explicitly set the array cell to null. Why? (on board) we are removing the reference (from the stack) to the object so that it can be garbage collected later. This is a minor potential memory leak but it’s worth plugging.

Linked-list based stack

Recall there are two basic ways to agglomerate data in Java: you can use arrays, or you can use references. For this second implementation, let’s consider using linked lists. Again, as we showed earlier in the semester, we’ll need a simple Node structure to link together into the list.

package stack;

public class Node<E> {
    private E value;
    private Node<E> next;

    public Node(E value, Node<E> next) {
        this.value = value;
        this.next = next;
    }

    public E getValue() {
        return value;
    }

    public Node<E> getNext() {
        return next;
    }
}

And since we’re only interested in the “top” of the stack, it’s pretty easy to do.

package stack;

public class LinkedStack<E> implements Stack<E> {

    private int size;
    private Node<E> head;

    public LinkedStack() {
        size = 0;
        head = null;
    }

    @Override
    public void push(E e) throws StackOverflowException {
        Node<E> node = new Node<E>(e, head);
        head = node;
        size++;
    }

    @Override
    public E peek() throws StackUnderflowException {
        if (isEmpty()) {
            throw new StackUnderflowException();
        }
        return head.getValue();
    }

    @Override
    public boolean isEmpty() {
        return head == null;
    }

    @Override
    public boolean isFull() {
        return false;
    }

    @Override
    public int size() {
        return 0;
    }
}

In-class exercise

Something’s missing. Can you write the pop method? Give it a serious effort here. Here’s what it should do (on board). Translate that to code.

public E pop() throws StackUnderflowException {
  if (isEmpty()) {
    throw new StackUnderflowException();
  }
  E item = head.getValue();
  head = head.getNext();
  size--;

  return item;
}

If you found this doable, then that’s great news for your prospects in 187.