This hasn’t been mentioned yet, the OTP Efficiency Guide specifically on maps. There are a number of other docs here that are super useful, too.
5.5 How Maps are Implemented
Internally, maps have two distinct representations depending on the number of elements in the map. The representation changes when a map grows beyond 32 elements, or when it shrinks to 32 elements or less.
A map with at most 32 elements has a compact representation, making it suitable as an alternative to records.
A map with more than 32 elements is represented as a tree that can be efficiently searched and updated regardless of how many elements there are.
gut feel is that erlang records will get you a long way here as already mentioned. tuples / records / arrays are fast.
process dictionary
For max speed, consider the process dictionary, a private per-process key-value store. The massive upside is it’s very very fast, but the massive downside is that it’s not possible to debug it – the state is lost if your process crashes, not like a normal stack trace would be.
One way to mitigate this is to have a function wrapping the larger transformation (a bit like a transaction), so you can at least see the arguments that caused your process to crash, and then you can debug at your leisure offline. As long as your recursive function isn’t calling out to other functions, your whole state can live in the process dictionary, and you can keep the data shuffling in your wrapper function.
Finally, OTP has a pile of alternative data structures GB trees (and GB sets) or arrays which are implemented as partially nested tuples, and a number of set related higher level functions, if your PEG can be expressed that way, including digraph which is again “just” ets. Maybe they’re more useful, maybe not. But they’re often not consulted.