CS 46B - Lecture 17

Cover page image

Pre-class reading

Doubly-Linked List

Removing at Head

Singly-Linked:

public class LinkedList
{
   . . .
   public Object removeFirst()
   {
      if (first == null) { throw new NoSuchElementException(); }
      Object element = first.data;
      first = first.next; 
      return element;
   }
   . . .
}

Lecture 17 Clicker Question 1

What do we need to do to make this method work for doubly-linked lists?

  1. Nothing—it will work correctly for doubly-linked lists as well
  2. Update the previous reference of the initial node after removal
  3. Update the last reference of the LinkedList if the last element was removed
  4. Something else

Removing at Head

public class LinkedList
{
   . . .
   public Object removeFirst()
   {
      if (first == null) { throw new NoSuchElementException(); }
      Object element = first.data;
      first = first.next; 
      if (first == null) { last = null; } // List is now empty
      else { first.previous = null; }
      return element;
   }
   . . .
}

Adding at Head

Singly-linked:

public void addFirst(Object element)
{
   Node newNode = new Node(); 
   newNode.data = element;
   newNode.next = first; 
   first = newNode; 
}

Lecture 17 Clicker Question 2

What do we need to do to make this method work for doubly-linked lists?

  1. Nothing—it will work correctly for doubly-linked lists as well
  2. Update the previous reference of the second node
  3. Update the last reference of the LinkedList if the last element was inserted
  4. Both 2 and 3

Adding at Head

public void addFirst(Object element)
{
   Node newNode = new Node(); 
   newNode.data = element;
   newNode.next = first; 
   if (first == null) { last = newNode; }
   else { first.previous = newNode; }
   first = newNode; 
}

Adding at Tail (New)

public void addLast(Object element)
{
   Node newNode = new Node();
   newNode.data = element;   
   newNode.previous = last;
   if (last == null) { first = newNode; }
   else { last.next = newNode; }
   last = newNode;
}

Lecture 17 Clicker Question 3

You've seen the “removing at head” method. We also need to implement “removing at tail”.

Does the “mirror image” technique from the previous slide work?

  1. Yes, it works perfectly
  2. It doesn't work for empty lists
  3. It doesn't work for lists with one element
  4. It doesn't work for lists of any size

Bidirectional Iterator

Stateful Remove

Moving Backwards

public Object previous()
{
   if (!hasPrevious()) { throw new NoSuchElementException(); }
   isAfterNext = false;
   isAfterPrevious = true;
   Object result = position.data;
   position = position.previous;
   return result;
}

remove

remove

Lecture 17 Clicker Question 4

In the singly-linked list implementation, the list iterator had two references: position and previous. What should be done about the previous reference in the doubly-linked case?

  1. It should be added, using the same code as in the singly-linked case
  2. It should be added, and a next reference should also be added to support backwards movement
  3. There is no need to add it since one can always get it as position.previous
  4. There is no need to add a previous reference, but we do need to add a boolean flag to tell whether the iterator is at the end of the list.

Testing the Implementation

Lecture 17 Clicker Question 5

In this program, leave the LinkedList class alone. In the LinkedListTest class, observe how the first six lines of the commented-out main method (from the textbook) are in a JUnit test method, just like on the preceding slide.

Add a second JUnit test method. Use the next five lines of the commented-out code.

Did your test case pass?

  1. Of course it passed
  2. No, I got an IllegalStateException
  3. No, I got a NoSuchElementException
  4. No, I got a NullPointerException

Helper Method for Checking List Contents

public static void check(String expected, LinkedList actual, String message)
{
   int n = expected.length();
   if (n > 0)
   {
      // Check first and last reference       
      assertEquals(message, expected.substring(0, 1), actual.getFirst());
      assertEquals(message, expected.substring(n - 1), actual.getLast());

      // Check next references
      ListIterator iter = actual.listIterator();
      for (int i = 0; i < n; i++)
      {
         assertTrue(message, iter.hasNext());
         assertEquals(message, expected.substring(i, i + 1), iter.next());
      }
      assertEquals(false, iter.hasNext());

      // Check previous references
      for (int i = n - 1 ; i >= 0; i--)
      {
         assertTrue(message, iter.hasPrevious());
         assertEquals(message, expected.substring(i, i + 1), iter.previous());
      }
      assertFalse(message, iter.hasPrevious());
   }
   else
   {
      // Check that first and last are null
      try
      {
         actual.getFirst();
         throw new IllegalStateException("first not null");
      }
      catch (NoSuchElementException ex) 
      {
      }

      try
      {
         actual.getLast();
         throw new IllegalStateException("last not null");
      }
      catch (NoSuchElementException ex)
      {
      }                
   }
} 

A Better Helper

Lecture 17 Clicker Question 6

Make those changes in the CodeCheck run that you just did. In LinkedList, remove private from the instance variables. In LinkedListTest, replace the check method with the one from the preceding slide. Repeat the test run. What happens now?

  1. Now both tests pass
  2. Now JUnit reports an assertion error
  3. The second test still fails with a NoSuchElementException
  4. The second test still fails, but with a different exception

Lecture 17 Clicker Question 7

Of course, the LinkedList code can't be quite the same code as in the book. The test should pass. Have a look at the addFirst method. Can you spot the error?

  1. Yes, it's obvious.
  2. I figured it out after drawing a diagram of the linked list.
  3. I could only figure it out by comparing the code with the code in the book.
  4. I wouldn't be able to figure this out in a million years.