Link

Stacks and Queues

Complete the Reading Quiz by noon before lecture.

Table of contents

  1. Representation Invariants
  2. Stack ADT
  3. Queue ADT
  4. 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. We examined 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?

In discussion, we explored the implementer’s perspective by analyzing, creating, and evaluating implementations of ArrayList and LinkedList. 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.

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 underly all data structures. A representation invariant is a condition that is guaranteed to be true during code execution (assuming there are no bugs in your code). As the implementer’s concern, invariants make it easier to reason about the correctness of our code as it gives us a convenient checklist every time we add a behavior to 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 towards the end of the course.

Invariants can also serve as guide posts for our problem-solving process and mental model of the data structure. We learned how to 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 and assumptions.

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 they all help to constrain the possible values of the data array and the integer size.

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, 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. Unlike lists, queues do not maintain item indexes: what matters is the position of each item relative to the front of the queue.

Generics

Generics force clients to supply a 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;
    }
}

The Box class can be made to take a generic type.

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