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.

\$\endgroup\$

1 Answer 1

0
\$\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.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.