What data structures can you use so you can get O(1) removal and replacement? Or how can you avoid situations when you need said structures?
- Anybody can ask a question
- Anybody can answer
- The best answers are voted up and rise to the top
There is a vast array of data structures exploiting laziness and other tricks to achieve amortized constant time or even (for some limited cases, such as queues) constant time updates for many kinds of problems. Chris Okasaki's PhD thesis "Purely Functional Data Structures" and book of the same name is a prime example (perhaps the first major one), but the field has advanced since. These data structures are typically not only purely functional in interface, but can also be implemented in pure Haskell and similar languages, and are fully persistent.
Even without any of these advanced tools, simple balanced binary search trees give logarithmic-time updates, so mutable memory can be simulated with at worst a logarithmic slow down.
There are other options, which may be considered cheating, but are very effective with regard to implementation effort and real-world performance. For example, linear types or uniqueness types allow in-place updating as implementation strategy for a conceptually pure language, by preventing the program from holding on to the previous value (the memory that would be mutated). This is less general than persistent data structures: For example, you can't easily build an undo log by storing all previous versions of the state. It's still a powerful tool, though AFAIK not yet available in the major functional languages.
Another option for safely introducing mutable state into a functional setting is the
One cheap mutable structure is argument stack.
Take a look at the typical SICP-style factorial calculation:
As you can see, the second argument to
This is, of course, a limited example.
You can get immutable linked lists with cheap replacement of the head node (and by extension any part beginning from the head): you just make the new head point to the same next node as the old head did. This works well with many list-processing algorithms (anything
You can get pretty good performance from associative arrays based e.g. on HAMTs. Logically you receive a new associative array with some key-value pair(s) changed. The implementation can share most of the common data between the old and the newly created objects. This is not O(1) though; usually you get something logarithmic, at least at worst case. Immutable trees, on the other hand, don't usually suffer any performance penalty compared to mutable trees. Of course, this requires some memory overhead, usually far from prohibitive.
Another approach is based on the idea that if a tree falls in a forest and no one hears it, it needs not produce sound. That is, if you can prove that a bit of mutated state never ever leaves some local scope, you can mutate data within it safely.
Clojure has transients that are mutable 'shadows' of immutable data structures that don't leak outside local scope. Clean uses Uniques to achieve something similar (if I remember correctly). Rust helps doing similar things with statically checked unique pointers.
What you're asking is a bit too broad. O(1) removal and replacement from which position? The head of a sequence? The tail? An arbitrary position? The data structure to use depends on those details. That said, 2-3 Finger Trees seem like one of the most versatile persistent data structures out there:
Generally persistent data structures have logarithmic performance when altering arbitrary positions. This may or may not be a problem, since the constant in an O(1) algorithm may be high, and the logarithmic slowdown might be "absorbed" into a slower overall algorithm.
More importantly, persistent data structures make reasoning about your program easier, and that should always be your default mode of operation. You should favor persistent data structures whenever possible, and only use a mutable data structure once you've profiled and determined that the persistent data structure is a performance bottleneck. Anything else is premature optimization.