18
votes

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?

5

5 Answers

43
votes

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.

8
votes

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

2
votes

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.

2
votes

To extract the name value from the list of maps

names = for %{name: n, id: _} <- list_with_maps, do: n
1
votes

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"]]