import java.util.NoSuchElementException; import java.util.Random; // to remove random element import java.util.ArrayList; // only used for sorting import java.util.Collections; /** Let's implement a linked list class in Java. For inspiration, we can * look at the Bag interface to see what functionality we may need. * This implementation includes: add, remove (2), size, toString. * The most interesting linked list methods are add, remove and combine. */ public class LL { private Node head; private Node tail; private int numNodes; // To create a brand new linked list means we start with no nodes. public LL() { head = null; tail = null; numNodes = 0; } // method to add a node to the list. The way this works is that someone // has to create the node, then call this function. public void add(Item newItem) { Node newNode = new Node(newItem); if (numNodes == 0) { head = newNode; tail = newNode; newNode.prev = null; newNode.next = null; } else { newNode.prev = tail; // tail used to be the last element newNode.next = null; tail.next = newNode; tail = newNode; } ++numNodes; } // We may have to traverse the list to find the victim, but first // we address more degenerate cases -- list is empty, or has just one // element; then see if the head is the one to remove, then // we look at the tail. If neither, then it's in the middle. public void remove(Item victim) { // Can't remove anything from an empty list. if (numNodes == 0) throw new NoSuchElementException(); // If just one element, make sure it matches the victim, then clear list. else if (numNodes == 1) { if (victim.equals(head.data)) { head = null; tail = null; --numNodes; } else throw new NoSuchElementException(); } // try the head else if (victim.equals(head.data)) { Node right = head.next; right.prev = null; head = right; --numNodes; } // try the tail else if (victim.equals(tail.data)) { Node left = tail.prev; left.next = null; tail = left; --numNodes; } // Otherwise it's in the middle. If not found at all, then throw // exception. Let's assume we are here only to remove one element. // So when we find a match, break from the loop. If someone wants to // remove all instances of some object, call this function in a loop. else { boolean found = false; for (Node n = head; n != null; n = n.next) { if (victim.equals(n.data)) { found = true; Node left = n.prev; Node right = n.next; left.next = right; right.prev = left; --numNodes; break; } } if (! found) throw new NoSuchElementException(); } } // Now, the fun part. Remove a random object. Complain if the list // is already empty. public void remove() { if (numNodes == 0) throw new NoSuchElementException(); Random gen = new Random(); int index = gen.nextInt(numNodes); // Traverse the list to count where the victim lives. // Then call the other remove function that already handles the // various cases. Node victim = head; for (int i = 0; i < index; ++i) victim = victim.next; remove(victim.data); } public int size() { return numNodes; } public String toString() { StringBuilder build = new StringBuilder(); build.append("head = " + head + "\ntail = " + tail + "\n"); for (Node n = head; n != null; n = n.next) { build.append("node " + n + " has prev = " + n.prev + ", next = " + n.next + ", data = " + n.data + "\n"); } return build.toString(); } // Combine a second list onto the end of me. This operation is simpler // than it would be for an array. public void combine(LL second) { tail.next = second.head; second.head.prev = tail; tail = second.tail; } public void give(Item i, LL other) { remove(i); other.add(i); } // Miscellaneous methods... // To compare, we first need to sort both lists. Then compare elements // one by one. We rarely need to compare two linked lists. public boolean equals(LL other) { if (size() != other.size()) return false; sort(); other.sort(); Node myNode, otherNode; for (myNode = head, otherNode = other.head; myNode != null; myNode = myNode.next, otherNode = otherNode.next) { if (! myNode.data.equals(otherNode.data)) return false; } return true; } // Sorting a linked list is not a pleasant operation to perform. For // better or worse, we copy all the elements into an ArrayList, use // Collections.sort, and then re-create the list. This may seem like // cheating, but the alternative is to manually implement a sort method. // Note that we will rarely need to sort a linked list. public void sort() { // if the size is 0 or 1, nothing really to sort if (numNodes < 2) return; ArrayList newList = new ArrayList(); for (Node n = head; n != null; n = n.next) newList.add(n.data); Collections.sort(newList, new ItemComparator()); // Now that we have a sorted ArrayList, re-create the linked list from it head = null; tail = null; numNodes = 0; for (int i = 0; i < newList.size(); ++i) add((Item)(newList.get(i))); } }