xgeek116
Fill a list after Enum.Each Loo^p
I’m trying to fill a list from the result of many loops (iterating another list) but the result is always empty here is my code :
list_a = []
list_b = []
Enum.each(list_c, fn(item) ->
{item_a, item_b} = process(item)
list_a ++ item_a.inner_list
list_b ++ item_b.inner_list
end)
For each iteration, item_a and item_b have a list named inner_list, so I tried to concatenate each time that list with the result lists but list_a and list_b are always empty.
Thanks in advance
Marked As Solved
al2o3cr
Like @LostKobrakai mentioned, scoping is one problem: code inside a do block can’t rebind variables outside of that block in a way that’s visible outside.
The other problem is that ++ makes a new list, it doesn’t mutate either argument. Code like what you posted is pretty common in other languages:
# in Ruby
list_a = []
list_b = []
list_c.each do |item|
item_a, item_b = process(item)
list_a.concat(item_a.inner_list)
list_b.concat(item_b.inner_list)
end
# in Python
list_a = []
list_b = []
for item in list_c:
item_a, item_b = process(item)
list_a.extend(item_a.inner_list)
list_b.extend(item_b.inner_list)
This approach will not work in Elixir. My suggestion is that you temporarily forget you even saw Enum.each; it’s only useful in a very narrow set of situations.
A better way to think about solving these kinds of problems in Elixir is to focus on how the “shape” of the data changes.
As a first step, we want to run process/1 on every element of list_c:
processed_list = Enum.map(list_c, &process/1)
Each element of processed_list is a tuple shaped like {item_a, item_b}.
There are several possibilities here: we could refine these tuples by extracting inner_list, but instead we’ll start by regrouping them.
What we have is a list of 2-element tuples.
What we want is a 2-element tuple of lists (what the original called list_a and list_b)
This kind of transformation comes up often enough that it has a name: Enum.unzip/1. Using it looks like:
tuple_of_lists = Enum.unzip(processed_list)
Now tuple_of_lists is shaped like {list_of_item_as, list_of_item_bs}
Finally, we need to extract the inner_list parts. We could use Enum.map again:
# not quite right - see below for a correct solution
{list_of_as, list_of_bs} = tuple_of_lists
list_a = Enum.map(list_of_as, & &1.inner_list)
list_b = Enum.map(list_of_bs, & &1.inner_list)
but this doesn’t quite do what we’re looking for: it results in list_a and list_b being lists of lists. You could use List.flatten/1, if the values in inner_list are not themselves lists.
BUT
There’s an easier way! Again, what we’re looking for is referenced enough to have a name: Enum.flat_map:
{list_of_as, list_of_bs} = tuple_of_lists
list_a = Enum.flat_map(list_of_as, & &1.inner_list)
list_b = Enum.flat_map(list_of_bs, & &1.inner_list)
Putting all the pieces together gives this code:
processed_list = Enum.map(list_c, &process/1)
tuple_of_lists = Enum.unzip(processed_list)
{list_of_as, list_of_bs} = tuple_of_lists
list_a = Enum.flat_map(list_of_as, & &1.inner_list)
list_b = Enum.flat_map(list_of_bs, & &1.inner_list)
This code can be tidied up with some Elixir syntax goodies:
# squish "processed_list" out of existence since it is bound and then immediately used
tuple_of_lists =
list_c
|> Enum.map(&process/1)
|> Enum.unzip()
{list_of_as, list_of_bs} = tuple_of_lists
list_a = Enum.flat_map(list_of_as, & &1.inner_list)
list_b = Enum.flat_map(list_of_bs, & &1.inner_list)
and even more with then which was recently added:
{list_a, list_b} =
list_c
|> Enum.map(&process/1)
|> Enum.unzip()
|> then(fn {list_of_as, list_of_bs} ->
{
Enum.flat_map(list_of_as, & &1.inner_list),
Enum.flat_map(list_of_bs, & &1.inner_list)
}
end)
This code does exactly the same steps as the first “un-simplified” version, but avoids having to name a bunch of variables that are used exactly once (like processed_items and tuple_of_lists).
Also Liked
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









