Return to lecture notes index

15-100 Lecture 26 (Monday, November 7, 2005)

Draft Notice

Quick Quiz

  // Add an "insertFirst" method to this container.
  // It should add the new item at index 0
  // Note: Yes, it does need to shift the rest back
  
  public boolean insertFirst (Object item) {

    // Okay if this wasn't done for the quiz -- then void return
    if (list.length == nextSlot) 
      return false;

    for (int index=nextSlot; index > 0; index--) {
      list[index] = list[index-1];
    }

    list[0] = item;
    nextSlot++;
  }
  

Today's Challenge

Last class we bumped into one nasty problem. If the array was "full", we were stuck. We returned false. But, that's not really user friendly. What happens next? Ouch!

Today, we're going to fix that by making our Container grow to accomodate more items, as needed. Now, we can't actually grow an array. And, managing multiple arrays would make a simple solution into a complex nightmare. So, we're going to have to solve this problem by slight of hand.

The Strategy

Fortunately, that's not as hard as it seems. Remember that "list" is not an array -- it is a reference variable. It identifies an array. The user accesses the array only indirectly through it. This is called "indirection". And, the great thing about it is that the user doesn't need to know which array object is being used -- just that it is referenced by "list".

This makes it possible for us to play the shell game and switch the array underneath list from the small, filled array to a new bigger one. And, the user of list will never know the difference.

So, the solution looks like this:

  1. We create a new,bigger array
  2. We copy each reference from the old array to the new array, keeping it at the correpsonding index. Item 0 to item 0, item 1 to item 1, &c.
  3. We change "list" to reference the new array instead of the old array
  4. The garbage collector is now free to take care of the unreferenced old array.
  5. The user can't tell the difference -- they are still using list and everything is still at the same position.

So, How Much Bigger?

So, our basic approach will be to create a new, bigger array to replace the old one. But, how much bigger? In a technical sense, we solve the problem if we make it only one slot larger -- then our insert can succeed.

But the problem is that we'll end up growing the array each time -- and that ain't cheap. Consider an array with 1,000,000,000 items. It it full, so we create one with 1,000,000,001 items. This involves copying the references for each fo the original 1,000,000,000 items. Now, we add one more, so we copy 1,000,000,001 items. And, each time we insert, we'll end up doing the same thing.

Ouch! So, we clearly want to grow by more than one. Typically these data structures grow by doubling. This way, there is plenty of room to grow. In 15-211 you'll learn that this approach leads to a reasonable average cost, known as :"amortized constant time". For now, we can just use our intuition and see that we end up copying a lot less this way. We're trading space for time -- a classic trade.

But, since this trade might be made differently if we knew that the array was large and wouldn't grow very often versus if it were small and dynamic, we make this growth factor a "configuration constant". A constant that can be changed in one place at compile time.

The grow() Method

The completed grow() method is below:

  private void grow() {

    Object[] biggerList = new Object[(int)(GROWTH_FACTOR * list.length)];

    for (int index=0; index < nextSlot; index++)
      biggerList[index] = list[index];

    list = biggerList;
  }

Using grow()

Now that we have the grow() method, how do we use it? Simple. In each case where we would otherwise have returned false because the array is full, we simply call grow() and continue. After grow(), the array will have plenty of space. Since there is no possibility of failure, these methods can no be "void".

Please consider add(), below, as an example:

  public void add (Object item) {

    if (nextSlot == list.length)
      grow();

    list[nextSlot++] = item;
  }

Container, so far


class Container {

  private Object[] list;
  private int nextSlot;
  
  private static final int DEFAULT_SIZE = 10;
  private static final double GROWTH_FACTOR = 2.0;
  private static final String NL = System.getProperty ("line_separator");
  

  public Container() {
    list = new Object[DEFAULT_SIZE];
    nextSlot = 0;
  }
  
  
  public Container (int size) {
    list = new Object[size];
    nextSlot = 0;
  }
  
  
  // Add an "insertFirst" method to this container.
  // It should add the new item at index 0
  // Note: Yes, it does need to shift the rest back 
  public boolean insertFirst (Object item) {
  
    if (nextSlot == list.length) 
      grow();
      
    for (int index=nextSlot; index > 0; index--) {
      list[index] = list[index-1];
    }
    
    list[0] = item;
    nextSlot++;
  }
  
  

  public void add (Object item) {
 
    if (nextSlot == list.length)
      grow();
  
    list[nextSlot++] = item;
  }
  
  
  public boolean contains (Object item) {
  
    for (int index=0; index < nextSlot; index++) {
      if (items[index].equals(item)) 
        return true;
    }
    
    return false;
  
  }
  
  public boolean remove (Object item) {
  
    int index;
  
    for (index=0; index < nextSlot; index++) {
    
      if (list[index].equals(item))
        break;  
    }
    
    if (index == nextSlot)
      return false;
    
    for (int hole=index; hole < (nextSlot-1); hole++) {
      list[hole] = list[hole+1];
      list[hole+1] = null;
    }
    
    nextSlot--;
  
  }
  
  
  private void grow() {
  
    Object[] biggerList = new Object[(int)(GROWTH_FACTOR * list.length)];
    
    for (int index=0; index < nextSlot; index++) 
      biggerList[index] = list[index];
      
    
    list = biggerList;
  
  
  }
  
  public String toString() {
  
    String rep = "";
    
    for (int index=0; index < nextSlot; index++) {
      rep += items[index] + NL;
    }
  }
}

Next Class

...we'll take a look at maintaining an ordered container.