Probably the most interesting property of a linked list is that splits and joins are both O(1)
, and neither of them allocates. There are times when this property is useful.
They’re also good when the data shape is mostly a list, but not entirely. Consider something like an undo/redo buffer: just a bunch of commands, they can be undone, and anything undone can be redone using the same struct. You can store this in an array, but if you do, you’re stuck: once the user does something new after an undo, all of the redo capability is lost, because you have to start overwriting it.
With a linked list, you can just split, turn it “sideways”, and make the alternate history a node in the main-line list. Of course, at that point, strictly speaking, you have a tree. But not really a tree, because your alternate history sticking off the side is data, you don’t reach it with the usual list traversal. That kind of flexibility can come in handy.
This also has the nice property that your place in the tree is an object, rather than the combination of an aggregate and an index. For this kind of complex system, that can be a lifesaver.
Linked lists are nice when the data is only a list sometimes. Let’s say you have macros. Just a list of commands (this is an array for our purposes), you send them off to whatever executes the commands, things happen, it finishes the macro.
Ok, but, what if the macro calls a macro?
Again, you can have a stack of macros here, implemented as an ArrayList or whatever we’re calling it, but now you’re always dealing with a stack, working off the top of it, when what you wanted to be doing is playing back a macro. Really you want the macro itself to be an ?*Macro
, so that you only execute it when there is one. This is less elegant with an array, although I’ll admit that checking if the array is empty does accomplish the same thing. The point is that macro-calls-macro is this inconvenient corner case which you don’t want to be constantly handling, the normal case is just one.
So you can add a prev
field to the macro struct. If your macro calls a macro, the current macro gets put in that prev
field of the new macro, and life goes on. Each time the command loop returns to the macro executor, it’s just executing a macro, and your code only has to deal with it as a stack when it has to.
Whenever you reach the end of a macro, you just check if there’s a stack, and pop it back in place if so. Since your prev
field is a ?*Macro
, and your execution is storing macros as a ?*Macro
, you just assign prev
and the next round of execution does whatever it needs to do.
Oh, and by the way, you might want to check that stack when an incoming macro arrives, just in case you’re executing a copy of it already. Vim doesn’t ^_^.
Linked lists are good for that kind of semi-principled ad-hoccery.