4
\$\begingroup\$

I would like to transform the input

elems = [a, b, c]
terms = [t1, t2]
@spec contains?(term, elem) :: boolean
def contains?(term, elem), do: #implementation

to either of

%{t1 => [a, b], t2 => [c]}

or

%{t1 => [a], t2 => [b, c]}

where

t1 |> contains?(a) #=> true
t1 |> contains?(b) #=> true
t1 |> contains?(c) #=> false
t2 |> contains?(a) #=> false
t2 |> contains?(b) #=> true
t2 |> contains?(c) #=> true

My current solution is as follows

defmodule Test do
 def contains?(term, elem) do
 elem in term
 end
 def test do
 elems = [1,2,3]
 terms = [[1,2], [2,3]]
 
 elems
 |> Enum.into(%{}, fn elem ->
 {elem,
 terms |> Enum.find(&contains?(&1, elem))}
 end)
 |> reverse_map()
 end
 defp reverse_map(map, reversed \\ %{})
 defp reverse_map(map, reversed) when map_size(map) == 0, do: reversed
 defp reverse_map(map, reversed) do
 [key | _] = Map.keys(map)
 {value, map} = Map.pop!(map, key)
 reversed = Map.update(reversed, value, [key], &[key | &1])
 reverse_map(map, reversed)
 end
end

With this solution I'm generating the map

%{a => t1, b => t1, c => t2}

then reversing it and collecting collisions in a list.

But I feel this intermediate map is unnecessary and a solution could exist without it.

In addition I'm not sure my implementation of reverse_map is as elegant as it could be.

asked Aug 3, 2020 at 2:45
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
defmodule Test do
 def contains?(term, elem) do
 elem in term
 end
 def test do
 elems = [1,2,3]
 terms = [[1,2], [2,3]]
 
 for e <- elems, reduce: %{} do
 acc ->
 key = Enum.find(terms, &contains?(&1, e))
 Map.update(acc, key, [e], &[e|&1])
 end 
 #=> %{[1, 2] => [2, 1], [2, 3] => [3]}
 end
end

Found a simple and elegant solution using a reduce comprehension.
Here, we iterate over each elem in elems, find the first term which contains it, then update our map with either term => [elem] or term => [elem|previous_elems] depending on if that key exists in the map yet.


Side note:
If I needed the elems in the same as order they were originally I could use & &1 ++ [e] instead of &[e|&1], but as I don't have that requirement it is more efficient to prepend the list.

answered Aug 5, 2020 at 8:17
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.