/**
* A list implemented using a doubly-linked circular list
* @author Edo Biagioni
* @lecture ICS 211 Feb 1 (or later)
* @date January 27, 2011
*/
public class DLinkedList<E> {
// here, include the DLinkedNode definition
/**
* A node in a doubly-linked list
* @author Edo Biagioni
* @lecture ICS 211 Feb 1
* @date January 27, 2010
* @bugs private class: include this code within a larger class
*/
private static class DLinkedNode<E> {
private E item;
private DLinkedNode<E> prev;
private DLinkedNode<E> next;
/**
* constructor to build a node with no successor
* @param the value to be stored by this node
*/
private DLinkedNode(E value) {
item = value;
next = null;
prev = null;
}
/**
* constructor to build a node with a specified (perhaps null) successor
* @param the value to be stored by this node
* @param the prev field for this node
* @param the next field for this node
*/
private DLinkedNode(E value, DLinkedNode<E> prev, DLinkedNode<E> next) {
item = value;
this.next = next;
this.prev = prev;
}
}
// this is the start of the linked list. If the list is empty, it is null
protected DLinkedNode<E> head;
// since this is a circular double-linked list, the tail node
// can always be found at head.prev, so we don't explicitly store it
protected int size;
// there are some relationships between the class variables. This
// method checks that these relationships always hold. Any
// property that always holds is called an invariant.
// the property may not hold in the middle of a method,
// so only call this at the beginning or end of a public method.
/**
* checks assertion
* @throws java.lang.AssertionError if the assertion is not true
*/
private void verify(boolean mustBeTrue) {
if (! mustBeTrue) {
throw new java.lang.AssertionError("assertion error");
}
}
/**
* checks class invariants
* @throws java.lang.AssertionError if the invariant is violated
*/
private void checkInvariants() {
// uncomment the next line to skip the checks:
// return;
// either head and tail are both null, or neither is null.
// size is zero if and only if they are null, and otherwise is positive
verify((size == 0) == (head == null));
verify(size >= 0);
if (size == 0) {
return; // no more checks
}
// check to make sure size is the same as the length of the list.
// this code takes O(n), so comment it out if performance is important
int measuredSize = 0;
DLinkedNode<E> node = head;
do {
node = node.next;
measuredSize++;
} while (node != head);
verify(measuredSize == size);
}
/**
* initializes an empty linked list
*/
public DLinkedList() {
head = null;
size = 0;
// one of the constructor's jobs is to make sure that the invariants hold.
checkInvariants();
}
/**
* adds a value to the end of the list
* @param the value to be added
* @return true (the add always succeeds)
*/
public boolean add(E value) {
checkInvariants();
DLinkedNode<E> newNode = new DLinkedNode<E>(value);
if (head == null) {
head = newNode;
newNode.next = head;
newNode.prev = head;
} else {
DLinkedNode<E> tail = head.prev;
tail.next = newNode;
head.prev = newNode;
newNode.next = head;
newNode.prev = tail;
}
size++;
checkInvariants();
return true;
}
/**
* returns the node at the requested position, may take time O(n)
* @param the position of the requested node, 0 for the head node
* @return the requested node
* @throws NullPointerException if the index is larger than the linked list
*/
private DLinkedNode<E> nodeAtPosition(int index) {
verify (index >= 0);
DLinkedNode<E> result = head;
while (index > 0) {
result = result.next;
index--;
}
verify (result != null);
return result;
}
/**
* removes a value from the given position in the list
* @param the position at which to add: 0 to add at the start
* @throws IndexOutOfBoundsException if the index is less than 0
* or greater than or equal to the number of elements in
* the linked list
*/
public E remove(int index) {
checkInvariants();
if ((index < 0) || (index >= size)) {
String badIndex =
new String ("index " + index + " must be between 0 and " + (size - 1));
throw new IndexOutOfBoundsException(badIndex);
}
// if head is null, by the invariants, either index < 0 or index >= size
// therefore head cannot be null
// just to be sure, let the program verify that
verify (head != null);
E value = head.item;
if (size == 1) {
head = null; // very simple!!
} else {
DLinkedNode<E> node = nodeAtPosition(index);
value = node.item; // return the value
if (index == 0) { // removing the head node
head = node.next; // new head node == old second node
}
node.prev.next = node.next; // get this node out of the list
node.next.prev = node.prev;
}
size--;
checkInvariants();
return value;
}
/**
* concatenates the elements of the linked list, separated by " ==> "
* @return the string representation of the list
*/
public String toString() {
checkInvariants();
DLinkedNode<E> node = head;
StringBuffer result = new StringBuffer();
if (head != null) {
while (true) {
result.append (node.item.toString());
node = node.next;
if (node == head) {
break; // entire list has been traversed
}
result.append (" ==> ");
}
}
checkInvariants(); // make sure we didn't break anything
return result.toString();
}
/**
* unit test method -- basic testing of the functionality
* @param required, ignored
*/
public static void main (String [] arguments) {
DLinkedList<String> dll = new DLinkedList<String>();
System.out.println (dll);
dll.add ("hello");
System.out.println (dll);
dll.add ("world");
System.out.println (dll);
dll.add ("foo");
System.out.println (dll);
dll.add ("bar");
System.out.println (dll);
dll.add ("baz");
System.out.println (dll);
dll.remove (2); // remove foo
System.out.println (dll);
dll.remove (3); // remove baz
System.out.println (dll);
dll.remove (1); // remove world
System.out.println (dll);
dll.remove (0); // remove hello
System.out.println (dll);
dll.remove (0); // remove hello
System.out.println (dll);
}
}