njwest
October 9, 2017, 10:33am
1
Still fairly new to Elixir and am coming from OOP, so bear with me here…
I have a list of lists of strings, each with a length of 2, ie:
list = [ ["Tony", "Glove"] , ["Mark", "Shirt"], ["Tony", "Hat"] ]
and I want to combine and dedup the lists that have the same value in index[0], so the list above would turn into:
convert_list(list) = [ ["Tony", "Glove", "Hat"], ["Mark", "Shirt"] ]
My first attempt was turning each list into a MapSet, then using:
if MapSet.member do _nothing else MapSet.union(Enum.at(list, x),Enum.at(list,y)
within a recursive for x <- 0..Enum.count(list)
function, but I am pretty much in over my head on this one.
Any help/guidance would be greatly appreciated!
njwest:
list = [ [“Tony”, “Glove”] , [“Mark”, “Shirt”], [“Tony”, “Hat”] ]
Whenever I need to check for key unicity, I would use a map. I would also use Enum.reduce… with some translation due to atom key. The last command line transform the map back to a list.
iex> list
|> Enum.reduce(%{}, fn [k, v], acc -> Map.update(acc, String.to_atom(k), [v], fn y -> [v | y] end) end)
|> Enum.map(fn {k, v} -> [to_string(k) | v] end)
[["Mark", "Shirt"], ["Tony", "Hat", "Glove"]]
You might need to reverse some order, as I always add to the head of a list [h | t]
1 Like
In fact, I don’t even need to transform the keys…
iex> list
|> Enum.reduce(%{}, fn [k, v], acc -> Map.update(acc, k, [v], fn y -> [v | y] end) end)
|> Enum.map(fn {k, v} -> [k | v] end)
1 Like
pma
October 9, 2017, 11:32am
4
kokolegorille:
list = [ [“Tony”, “Glove”] , [“Mark”, “Shirt”], [“Tony”, “Hat”] ]
To guarantee list values are deduped one option is to use a MapSet as collector, or just add Enum.uniq/1
to previous solution.
list
|> Enum.reduce(%{}, fn [k, v], m -> Map.update(m, k, MapSet.new([v]), &MapSet.put(&1, v)) end)
|> Enum.map(fn {k, v} -> [k | Enum.to_list(v)] end)
2 Likes
That is true, your solution is nicer with the use of MapSet
1 Like
njwest
October 9, 2017, 12:11pm
6
Fascinating, thank you both for the help! I am starting to wrap my head around accumulators and Enum.reduce now.
One more layer of complexity: what if there is another list of items, where it is possible for items to belong to items that belong to a person, ie:
list2 = [ ["Tony", "Glove"] , ["Mark", "Shirt"], ["Tony", "Wallet"], ["Wallet","Money" ] ]
How would you pipe in another Enum.reduce/another function to get:
convert_list2(list2) = [ ["Tony", "Glove", "Wallet","Money" ], ["Mark", "Shirt"] ]
Or would this just require a different initial Enum.reduce?
That is more complex.
A hint would be to get all root keys, by filtering the keys that does not exists in the list of values.
values = list2 |> Enum.map(& List.last(&1))
root_keys = list2 |> Enum.map(& List.first(&1)) |> Enum.filter(& !Enum.member?(values, &1)) |> Enum.uniq
Then, in some loop, treat the root keys as is, and the others as belonging to a sub tree.
Not to mention edge case like this…
[“Mark”, “Wallet”], [“Tony”, “Wallet”]
1 Like
What immediately jumps to my mind is:
╰─➤ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> list = [ ["Tony", "Glove"] , ["Mark", "Shirt"], ["Tony", "Hat"] ]
[["Tony", "Glove"], ["Mark", "Shirt"], ["Tony", "Hat"]]
iex(2)> list |> Enum.group_by(&hd/1) |> Enum.map(&Enum.uniq(List.flatten(elem(&1, 1))))
[["Mark", "Shirt"], ["Tony", "Glove", "Hat"]]
iex(3)>
2 Likes