class Node { public Object data; public Node next; public Node previous; }
public class LinkedList { private Node first; private Node last; ... }
Singly-Linked:
public class LinkedList { . . . public Object removeFirst() { if (first == null) { throw new NoSuchElementException(); } Object element = first.data; first = first.next; return element; } . . . }
What do we need to do to make this method work for doubly-linked lists?
previous
reference of the initial node after removallast
reference of the LinkedList
if the last element was removedpublic 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; } . . . }
Singly-linked:
public void addFirst(Object element) { Node newNode = new Node(); newNode.data = element; newNode.next = first; first = newNode; }
What do we need to do to make this method work for doubly-linked lists?
previous
reference of the second nodelast
reference of the LinkedList
if the last element was insertedpublic 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; }
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; }
addFirst
first
and last
previous
and next
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?
previous
methodLinkedList lst = new LinkedList(); lst.addLast("A"); lst.addLast("B"); lst.addLast("C"); ListIterator iter = lst.listIterator(); // The iterator is before the first element |ABC iter.next(); // Returns “A”; the iterator is after the first element A|BC iter.next(); // Returns “B”; the iterator is after the second element AB|C iter.previous(); // Returns “B”; the iterator is after the first element A|BC
remove
removes the last element that was returned by next
or previous
remove
—add
adds before the iteratorremove
just remove the element before the iterator? while (iter.hasNext())
if (we don't like iter.next()) iter.remove();
while (iter.hasPrevious())
if (we don't like iter.previous()) iter.remove();
public Object previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } isAfterNext = false; isAfterPrevious = true; Object result = position.data; position = position.previous; return result; }
isAfterNext
, isAfterPrevious
flagsremove
private Node lastPosition() { if (isAfterNext) { return position; } else if (isAfterPrevious) { if (position == null) { return first; } else { return position.next; } } else { throw new IllegalStateException(); } }
remove
Node positionToRemove = lastPosition();
positionToRemove.previous.next = positionToRemove.next; positionToRemove.next.previous = positionToRemove.previous;
if (isAfterNext) { position = position.previous; }
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?
next
reference should also be added to support backwards movementposition.previous
previous
reference, but we do need to add a boolean
flag to tell whether the iterator is at the end of the list.@Test public void testAddLast() { LinkedList lst = new LinkedList(); check("", lst, "Constructing empty list"); lst.addLast("A"); check("A", lst, "Adding last to empty list"); lst.addLast("B"); check("AB", lst, "Adding last to non-empty list"); }
The check
method tests that the list contains the letters in the string, and that all links are intact.
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?
check
methodpublic 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) { } } }
public
or private
, then any method in the same package can access.public class LinkedList {privateNode first;privateNode last;
public static void check(String expected, LinkedList actual, String message) { int n = expected.length(); if (n > 0) { int i = 0; for (LinkedList.Node current = actual.first; current != null; current = current.next) { assertEquals(message, expected.substring(i, i + 1), current.data); i++; } assertEquals(message, n, i); for (LinkedList.Node current = actual.last; current != null; current = current.previous) { i--; assertEquals(message, expected.substring(i, i + 1), current.data); } assertEquals(message, 0, i); } else { assertNull(message, actual.first); assertNull(message, actual.last); } }
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?
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?