CSC153 2004S, Class 45: Linked Lists Admin: * Exam 3 due * Cool talk today at 4:15 (snacks) / 4:30 (talk) * Unknown temperature convo tomorrow * *Short* report on Monday's talk? Overview: * Detour: Implementing isValid * Linked lists: Implementing lists with nodes. * Implementing basic methods * Implementing ordered methods * Cool tricks Question: What is the thesis of "Lists with Current Considered Harmful"? * Teachers are responsible for teaching good design * Look, LinkedList without a generic List interface is STUPID * The title, which means: * Having methods that deal with "the current position in the list" is a bad idea * A better idea is ... * A separate class that acts as an iterator * Problems with the design in that paper? * Claim: Suppose you want to sort the list "in place": You need two iterators and a "swap" method * Assumes that lists maintain their order. Observation: In the GeneralList interface, many of the position-based methods have as a prerequisite that the position is valid * What makes a position valid? * Is there a corresponding space in the list? (For array-based lists, whether or not the index is less than the length and there's something there.) * What makes a position invalid? * Removing something from the list *supposedly* invalidates all active positions * Adding something to the list *supposedly* invalidates all active positions * How does the client programmer know whether or not a position is valid? E.g., in a list with at least three elements Position whatever = list.getFirst(); whatever = list.next(whatever); whatever = list.next(whatever); ... // Can I now call get(whatever)? * An inefficient answer: Retraverse the list and compare each position to whatever * A better answer: Carefully analyze the program to ensure that you can successfully call get * A "pass the buck" answer: Call isValid() before calling get. * If you analyze your code carefully, you don't need to call isValid() * If you don't analyze your code carefully, you deal with the added inefficiency of isValid() * A "Saul" answer: get() should return null on invalid positions * Makes your code less efficient * We now have to implement isValid(Position pos) for all of our lists. How can we implement it well (efficiently)? * A lookup table of all valid positions. O(log2n) * The 'Chase' solution: Iterate through positions until you find one that matches O(n) * 'Ilan's Stupid' claim: For array-based lists, a position is valid if the index is less than the length: But a position could be implicitly bad but still a valid number * Suppose we are "slowly" iterating the list, trying to look at all values, but we do some "other stuff" in the middle. If we are at position 4 and delete the thing at position 3, the iteration * A semi-dangerous O(1) solution: Include a "version" integer in each list and each position. Increment it in the list when you change the list. If the version of a position is less than the version of the list, it is invalid. class LinkedList { long version = 0; Node front; void add(Object addMePlease) { ++version; ... } Position getFirst() { return new LinkedListPosition(version, front); } public boolean isValid(Position pos) { return ((LinkedListPosition) pos).version == this.version; } } class Node { Object contents; Node next; } class LinkedListPosition implements Position { Node hereIam; int version; public LinkedListPosition(int version, Node here) { this.hereIam = here; this.version = version; } }