The pointers that hold us together
“What appeared to be fine golden chains wove around the pictures, linking them together, but after examining them for a minute or so, Harry realized that the chains were actually one word, repeated a thousand times in golden ink: friends…friends…friends…” — J.K. Rowling, Harry Potter and the Deathly Hallows
This week in computer science: flexible lists!
Linked lists are an abstract data type that are enormously useful for large (and I mean large) data sets. A list of 10’000 numbers is very unwieldy to work with; a linked list is malleable, efficient (on that scale), and quite sensical.
I had only one issue with one of the coding decisions made in lecture this week: the way that __str__ works for linked list nodes. In class, Danny wrote his LinkedListNode’s __str__ method to return not only the value of the node self, but also of every node following it (walking along the list to produce the entirety of the rest of the list). This is very much in line with the view of linked lists as nodes that each contain the rest of the list, but it contradicts (in my opinion) the view that a linked list is simply a series of nodes linked together by pointers – which is the view that we are supposedly taking in 148. If a node knows only itself (and a pointer that points outside of itself), then oughtn't the node’s __str__ method return only that node’s value? And I believe that the walking along the list to collect every node and its next one should be done at the LinkedList class level __str__ instead. Just my two cents. Danny apparently doesn't agree, but that’s ok ;)
What we did this week (not only including linked lists, but also the related stack and derived queue data types) was focused on the LIFO (last in, first out) nature of lists (or, for queues, FIFO: first in, first out). But we didn't really touch on a rather important thing that linked lists can be made to support: walking the list in reverse.
For a regular list, this isn't too exciting; list slicing allows for negative indices – no sweat. But for linked lists (I mean normal, singly linked lists), where finding one node requires walking over the entirety of the list before it (taking up huge amounts of time and memory), the invention of the doubly linked list is momentous. By simply adding another variable to every node in the list: a self.previous in addition to the already present self.next_, you can follow nodes backwards. This means that methods like delete_last are suddenly so much easier, because instead of walking along the whole list to get to the second-last node to unlink it (which for long lists is insanely inefficient), you just walk one node back from the doubly linked list’s self.back. It also makes node retrieval easier: If you’re looking for the value in node 250 of a 300-node list, a doubly linked list can be coded to walk backwards from 300 than forwards from 0 – it’s just that much faster.
And now that I'm thinking about walking back and forth on a linked list, it brings the question to mind: what about a circular list? Does that even make sense? (I think it does. Just make the “front” and “back” be the same thing – with a different name, of course – and call each node a “spoke” – like spokes on a wheel.) But what would a circle of “spokes” be used for? Now that’s the real question. ADTs without actual uses are pretty pointless, no? I’ll keep thinking. There must be a cool way to use a spoke-list. (Google’s answers, which involve “timesharing” and “resource allocation”, do not count as cool ways.)