odohMei7
Append to list after Enum.map. How to do this efficiently?
How can I efficiently append one entry to a list? Is this the best solution:
history = [{1,100}, {2,300}, {3,200}]
current = {4,150}
chart = Enum.map(history,&Tuple.to_list(&1)) ++ [Tuple.to_list(current)]|>Jason.encode!
It does not feel good to scan the list twice, once during Enum.map and once for appending to the list which is O(length(list)) as I have read. Is there a better method to only loop through the list once (think of a long history list)?
I did not benchmark and what I have done above seems to be sufficiently performant in my use case so this is rather a theoretical question that I am interested in.
Bonus question: Is there any method to avoid the Tuple.to_list before I pipe into Jason.encode, this also feels unnecessary but unfortunately Jason.encode cannot handle tuples.
Marked As Solved
sabiwara
From a purely theoretical perspective, you could implement a variant of Enum.map/2 that appends an element at the end, such as:
def map_append([], _fun, last), do: [last]
def map_append([head | tail], fun, last) do
[fun.(head) | map_append(tail, fun, last)]
end
But as you pointed out, this is probably premature optimization and I wouldn’t recommend doing this in production code ![]()
Also Liked
l00ker
I would like to mention that Enum.reverse in turn calls :lists.reverse if the first argument (the enumerable) is a List.
With that said, after reading the following in Programming Erlang (2nd edition) by Joe Armstrong, I felt much better about using Enum.reverse with a List as the enumerable:
…
If the order matters, then call lists:reverse/1, which is highly optimized.
…Note: Whenever you want to reverse a list, you should call lists:reverse and nothing else. If you look in the source code for the module lists, you’ll find a definition of reverse. However, this definition is simply used for illustration. The compiler, when it finds a call to lists:reverse, calls a more efficient internal version of the function.
So, with a long list, most of the time I’ll always add elements to a list head and then call Enum.reverse at the end and trust that Joe and Erlang have my back. ![]()
But as Joe points out a little later:
The best thing to do is first write your programs as clearly as possible and then, if there are performance problems, measure before making any optimizations.
lud
Any solution I could imagine would be a wrapper around Tuple.to_list anyway.
For the double scan, unless your history is very, very long, I would not try to optimize that, what you do is fine. If your really want a performance optimization here then I would just build the history in reverse order, and prepend the new entry instead of appending it.
sabiwara
Just like Enum.map/2 (actually :lists.map/2), this implementation is body recursive, not tail recursive.
A tail-recursive version would need to call Enum.reverse/2 at the end, just like the example provided by @eksperimental.
Either one is probably fine in this case and the performance should be roughly equivalent when building lists, cf the Erlang efficiency guide and the article it refers to. Body recursion might even be faster / use less memory depending on the function being optimized, list size, your architecture etc…
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









