Filtering values out of a list of maps by their map key

I have a list of maps that may in the future have multiple different keys-pair values inside and the key that I am looking for may not exist in each map, this right here is my current problem. I can currently get the correct values when the key does exist inside a map, but when it doesn’t exist I get an error. I will show below.

def get_company_case_totals(cases) do
        cases
	|> Enum.map( fn %{casetype: c} -> c end)
end
The Test I am developing from
test "get_company_case_totals/1 with dummy case list" do
	dummy_data = [
		%{casetype: "dummy1"},
		%{casetype: "dummy1"},
		%{casetype: "dummy1"},
		%{casetype: "dummy2"},
		%{casetype: "dummy2"},
		%{casetype: "dummy2"},
		%{casetype: "dummy3"},
		%{casetype: "dummy3"},
		%{casetype: "dummy4"},
		%{casetype: "dummy1"},
		%{casetype: "dummy1"},
	]
	correct_resp = %{"dummy1" => 5, "dummy2" => 3, "dummy3" => 2, "dummy4" => 1}		
        fake_type = dummy_data ++ %{faketype: "dummy1"}

	assert Reporting.get_company_case_totals(dummy_data) == correct_resp
	assert Reporting.get_company_case_totals(fake_type) == correct_resp

end

So the first assert works correctly because each map in the list contains the casetype: key, but when I add the last map i get this error when running Mix Test.

test 'tablename' get_company_case_totals/1 with dummy case list (PhoenixApi.ReportingTest)
test/phoenixApi/reporting_test.exs:165
** (FunctionClauseError) no function clause matching in Enum."-map/2-lists^map/1-0-"/2

The following arguments were given to Enum."-map/2-lists^map/1-0-"/2:
     
    # 1
    #Function<5.56924159/1 in PhoenixApi.Reporting.get_company_case_totals/2>
  
    # 2
    %{faketype: "dummy1"}
     
code: assert Reporting.get_company_case_totals(fake_type) == %{"dummy1" => 5, "dummy2" => 3, "dummy3" => 2, "dummy4" => 1}
stacktrace:
  (elixir 1.11.2) lib/enum.ex:1399: Enum."-map/2-lists^map/1-0-"/2
  (elixir 1.11.2) lib/enum.ex:1399: Enum."-map/2-lists^map/1-0-"/2
  (phoenixApi 0.1.0) lib/phoenixApi/contexts/reporting.ex:231: PhoenixApi.Reporting.get_company_case_totals/1
test/phoenixApi/reporting_test.exs:183: (test)

At the end of the day I wish to be able to go through a list of maps and only retrieve the values with the key ‘casetype:’ but because there is no key in the map passed to Enum.map it throws the above error.

Thank you for reading :smiley:

The solved solution:

def get_company_case_totals(cases) do
	cases
	|> Enum.filter(& &1[:casetype])
	|> Enum.map( fn %{casetype: c} -> c end)
end

Hello and welcome,

If You use map You have a soft way to check for keys…

iex(1)> map = %{faketype: "dummy1"}
iex(2)> map[:casetype]
nil
iex(3)> map[:faketype]
"dummy1"

It won’t fail if the key is not present.

You can filter list to get elements with given key like this.

cases
|> Enum.filter(& &1[:casetype])

#etc.

You can also use Map.has_key?/2

2 Likes

the exception happens because your functions needs a catch all clause at the end.

1 Like

Thank you, I am trying to avoid try catch type coding because I have heard its unidiomatic?
Therefore I tried a case statement instead to catch either the error or the key I wanted, but this didn’t work due my inability to pattern match within the Map statement.
I have used @kokolegorille 's answer and it is working perfectly.

A catch all clause is not a try and catch…

It would look like this.

fn 
  %{casetype: c} -> c 
  _ -> nil  # <- catch all clause
end

But in this case, You might want to clean nil value in the result.

Ahh, I have just now tried that out:

iex(2)> map = [%{faketype: "dumm"}, %{casetype: "dumm"}, %{casetype: "dumm2"}, %{casetype: "dumm"}, %{faketype: "d"}, %{casetype: "dumm"}]
[
  %{faketype: "dumm"},
  %{casetype: "dumm"},
  %{casetype: "dumm2"},
  %{casetype: "dumm"},
  %{faketype: "d"},
  %{casetype: "dumm"}
]
iex(3)> map |> Enum.map(fn something -> case something do                                      
...(3)> %{casetype: c} -> c
...(3)> _ -> nil
...(3)> end
...(3)> end)
[nil, "dumm", "dumm2", "dumm", nil, "dumm"]

Is there a better way to format this map call?
Thank you, + @eksperimental

You don’t need the case, an anonymous function with multiple heads will do, like the one I wrote. If You don’t want to accumulate nil value, You should filter first…

2 Likes

Sorry, but I fail to see what your code is trying to do. Maybe you could elaborate on it a bit more so I can give you my opinion

It was my shortcoming, I now understand what you meant, and it has helped me, I have also edited the code that I have posted to reflect this.
Thank you :slight_smile: