I recently was in need of swapping the values at two positions in a list.
Of course, linked lists are not the best data type to use when you have to swap elements a lot, but I believe that it is a rather common problem that you have a list, and you need to swap the positions of two elements.
As implementing an efficient version of this function is not trivial, I tried a couple of approaches, and wanted to share the results here.
I think it might be useful to add this functionality to the List module.
I've benchmarked four different solutions;
1. The naïve approach of using Enum.fetch! and List.replace_at. This runs in O(4n) and as could be seen from the benchmarks, it is rather slow.
2. An approach using maps and Enum.with_index and a for-comprehension to transform a list into an index-keyed map on which the replacements are in O(log n). Of course, the three transformation steps make this function more expensive (so time complexity is something like O(3n+3log(n)). In the benchmark this was the slowest solution.
3. An approach using plain recursion to build up a map. (I expect that the time complexity is somewhat akin O(2n+3log(n)) ) This is slightly faster than (2) but still slower than the final solution.
4. An approach where the input list is split at the given indices, and the final result is built back up by taking the heads/tails of these different split segments. I believe this solution to be O(2n), and this is indeed the fastest during my benchmarking.
Of course, my approach is not extremely scientific, so you might want to test this out for yourself. The code including the benchmark setup I used
can be found on Github.
This is the source code of the fastest solution:
def split_swap(list, pos_a, pos_a), do: list
def split_swap(list, pos_a, pos_b) when pos_a < pos_b do
{initial, rest} = Enum.split(list, pos_a)
{between, tail} = Enum.split(rest, pos_b - pos_a)
a = hd(between)
b = hd(tail)
initial ++ [b] ++ tl(between) ++ [a] ++ tl(tail)
end
def split_swap(list, pos_a, pos_b) when pos_b < pos_a, do: split_swap(list, pos_b, pos_a)
Is it a good idea to add this as a function called e.g. `swap` to the List module?
Sincerely,
~Wiebe-Marten