Reputation: 8106
Being new to Elixir I'm having some problems understanding pattern matching.
If I have an Elixir data structure like this:
list_with_maps = [%{"id" => 1, "name" => "a"}, %{"id" => 2, "name" => "b"}]
What would be the best way to get values of all id fields from it?
Upvotes: 20
Views: 27445
Reputation: 1019
Another option is to use the get_in
function and leveraging the Access
module. The Access module lets you build expressive ways of dealing with nested data structures.
To get a list of values at "id"
you could run:
get_in(list_with_maps, [Access.all, "id"])
Passing Access.all
as the first element in the list, followed by "id"
tells get_in
to return all list_with_maps
's "id"
value.
It may seem like overkill for this example, but if your structure looked like:
list_with_maps = [%{"id" => 1, "identities" => [%{"name" => "a"}, %{"name" => "Secret A"}]}, %{"id" => 2, "identities" => [%{"name" => "b"},%{"name" => "Secret B"}]}]
We could get all of the names with:
get_in(list_with_maps, [Access.all, "identities", Access.all, "name"])
# [["a", "Secret A"], ["b", "Secret B"]]
Upvotes: 3
Reputation: 405
To extract the name value from the list of maps
names = for %{name: n, id: _} <- list_with_maps, do: n
Upvotes: 2
Reputation: 23310
A more generic (and simpler) way of getting a subset of the keys of a Map
is to use Map.take/2
, which you can use like this:
map = %{"id" => 1, "name" => "a"}
Map.take(map, ["id"])
> %{"id" => 1}
As you can see, it takes an array of keys and returns a new map with only the keys you want.
Now, applying this to a list is as simple as using a map, and then using the Map.take/2
in mapper function. As has been pointed out, you can do this using either a lambda:
Enum.map(list_with_maps, fn (map) -> Map.take(map, ["id"]) end)
Or you can use a capture:
Enum.map(list_with_maps, &(Map.take(&1, ["id"])))
This will create more intermediate maps, but for most situations that won't be a problem, as Elixir is pretty smart about memory re-usage and won't actually create these objects a lot of the times, unles
Upvotes: 10
Reputation: 15343
For sake of completeness for the answers for this question you could also do something like this:
defmodule Test do
def get_all_ids([head | tail ]) do
IO.puts head["id"]
get_all_ids(tail)
end
def get_all_ids([]) do
IO.puts "end"
end
end
Which would be used like so:
iex(7)> Test.get_all_ids(list_with_maps)
1
2
end
:ok
Although I think @Gazler's answer is the better answer in this case.
Oh and since you specifically mentioned pattern matching, this would also work:
defmodule Test do
def get_all_ids([%{"id" => id} = m | tail ]) do
IO.puts id
get_all_ids(tail)
end
def get_all_ids([]) do
IO.puts "end"
end
end
The call would be exactly the same; the difference in the second approach is that it's using a pattern match to parse the map in the argument list.
You might also change the argument list in this line: def get_all_ids([%{"id" => id} = m | tail ]) do
to this: def get_all_ids([%{"id" => id} = _m | tail ]) do
just to avoid the warning about m
being unused.
Upvotes: 2
Reputation: 84190
You can map over the list and return the id using Enum.map/2
Enum.map(list_with_maps, fn (x) -> x["id"] end)
[1, 2]
You can write the same function using the capture operator:
Enum.map(list_with_maps, & &1["id"])
I prefer writing & &1["id"]
as &(&1["id"])
but the parentheses are optional.
Upvotes: 47