Link

Stacks and Queues

Complete the Reading Quiz by 3:00pm before lecture.

Table of contents

  1. Representation Invariants
  2. Stack ADT
  3. Queue ADT
  4. Java Generics

In lecture, we learned that abstract data types (ADTs) hide implementation details from clients, the users of ADTs. This holds so long as both the client and the implementer agree on the ADT’s possible values and operations.

Here, we examine design decisions from the client’s perspective: Given that we want to represent the data using the List ADT, which of either ArrayList or LinkedList should we use to solve the problem? Recall that ArrayList is backed by a data array of items that we resize when data.length is not large enough for the size of the list. Let’s take a closer look at the get and removeFront methods for our ArrayList.

Item get(int i)
Returns the item at the specified zero-based index i in the list.
removeFront(int k)
Removes the first k values from the list.

Implementing get by indexing directly into the underlying data array relies on the assumption that the i-th item of the list is always located at data[i]. While this assumption might seem simple, it dictates the implementation for removeFront.

Suppose we decide to implement `removeFront` by setting the first k items of the data array to null. Why will this implementation break the `get` method?

After setting the first k items of the data array to null, the first item in the list is now at data[k] rather than data[0]. get would now need to return data[k + i] to return the i-th item of the list.

Representation Invariants

Assumptions like this one are called representation invariants and underlie all concrete data structures. A representation invariant is a condition that is guaranteed to be true before and after the data structure is modified. Invariants make it easier to reason about the correctness of our code as it gives us a convenient checklist every time we modify our data structure.

Do representation invariants also concern ADT clients?

Representation invariants should not concern ADT clients because they are properties of the particular data structure implementation, not the abstract data type. For example, the invariant for item position in an array applies to ArrayList, not LinkedList. Things get more complicated in the real world due to The Law of Leaky Abstractions: “All non-trivial abstractions, to some degree, are leaky.” We’ll revisit this idea throughout the course.

Invariants can also serve as guide posts for our problem-solving process and mental model of the data structure. We could implement removeFront by making a plan that integrated information about the ArrayList invariants. For every program we want to analyze, create, or evaluate, we should ask ourselves about its invariants.

What are the representation invariants of the ArrayList data structure? Consider both the data array as well as the integer size attribute. Be as comprehensive as possible.

There are many different ways to define the invariants, but all possible invariants will constrain the possible values of the data array and the integer size. These constraints will help us write code that we know will be correct and bug free; in fact, many experienced programmers implement the code from the representation variant rather than defining the representation invariant from the implementation!

Possible values of size
size is always the non-negative integer number of items in the ArrayList.
Possible values of data
data is an array of items, never null. The i-th item in the list is always stored in data[i].

By choosing this set of invariants, we simplify the implementation of get but complicate the implementation of removeFront.

Stack ADT

The Stack is a linear ADT that supports adding and removing items from one end that we call the top of the stack, or “last-in, first-out” (LIFO).

push(Item item)
Puts the item on the top of the stack.
Item pop()
Removes and returns the top item of the stack.

This is like only using the addLast and removeLast methods of a List ADT, where the back of the list represents the top of the stack. Unlike lists, stacks do not maintain item indexes: what matters is the position of each item relative to the top of the stack.

Queue ADT

The Queue is a linear ADT that supports adding items to one end and removing items from the other end of the queue, or “first-in, first-out” (FIFO).

add(Item item)
Adds the item to the back of the queue.
Item remove()
Removes and returns the item at the front of the queue.

This is like only using the addLast and removeFront methods of a List ADT. Unlike lists, queues do not maintain item indexes: what matters is the position of each item relative to the front of the queue.

Java Generics

Java generics allow you to implement a class independent of a specific type. It is only when the generic is used (ie, instantiated) that clients must supply the type. Consider the following Box class.

public class Box {
    private Object object;

    public void set(Object object) {
        this.object = object;
    }

    public Object get() {
        return object;
    }
}

Since every Java type derives from Object, this implementation can contain any time. However, we can also use generics to implement a similar Box.

public class Box<T> {
    // T stands for Type
    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

Reading Quiz