Pattern Matching and Updating Maps

Learn to perform pattern matching with maps and to update them.

The question we most often ask of our maps is, “Do you have the following keys (and maybe values)?” For example, let’s use this map: person = %{ name: "Dave", height: 1.88 }.

  • Is there an entry with the key :name?
   iex> %{ name: a_name } = person 
   %{height: 1.88, name: "Dave"} 
   iex> a_name
   "Dave"
  • Are there entries for the keys :name and :height?
   iex> %{ name: _, height: _ } = person
   %{height: 1.88, name: "Dave"}
  • Does the entry with key :name have the value "Dave"?
   iex> %{ name: "Dave" } = person
   %{height: 1.88, name: "Dave"}

Our map doesn’t have the key :weight, so the following pattern match fails:

   iex> %{ name: _, weight: _ } = person
   ** (MatchError) no match of right hand side value: %{height: 1.88, name: "Dave"}

It’s worth noting how the first pattern match destructured the map, extracting the value associated with the key :name. We can use this in many ways. The following example uses for to iterate over a list of people. Destructuring is used to extract the height value, which is used to filter the results.

Click the “Run” button to see the output.

defmodule First.MixProject do
  use Mix.Project

  def project do
    [
      app: :first,
      version: "0.1.0",
      elixir: "~> 1.12",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
    ]
  end

  def hello do
    [
    IO.puts("Hello")
    ]
  end
end
Pattern matching example

In this code, we see the following:

  • We feed a list of maps to our comprehension.
  • The generator clause binds each map (as a whole) to person and binds the height from that map to height.
  • The filter selects only those maps where the height exceeds 1.5, and the do block returns the people that match.
  • The comprehension as a whole returns a list of these people, which IO.inspect prints.

Pattern matching can’t bind keys

We can’t bind a value to a key during pattern matching. We can write this:

iex> %{ 2 => state } = %{ 1 => :ok, 2 => :error } 
%{1 => :ok, 2 => :error}
iex> state
:error

But we can’t write this:

iex> %{ item => :ok } = %{ 1 => :ok, 2 => :error }
** (CompileError) iex:5: illegal use of variable item in map key...

Pattern matching can match variable keys

When we looked at basic pattern matching, we saw that the ^ operator uses the value already in a variable on the left-hand side of a match. We can do the same with the keys of a map:

iex> data = %{ name: "Dave", state: "TX", likes: "Elixir" } 
%{likes: "Elixir", name: "Dave", state: "TX"}
iex> for key <- [ :name, :likes ] do
...>   %{ ^key => value } = data
...>   value
...> end
["Dave", "Elixir"]

Update a map

Maps let us add new key-value entries and update existing entries without traversing the whole structure. But as with all values in Elixir, a map is immutable, so the result of the update is a new map. The simplest way to update a map is with this syntax: new_map = %{ old_map | key => value, ... }.

This creates a new map that’s a copy of the old one, but the values associated with the keys on the right of the pipe character are updated:

iex> m = %{ a: 1, b: 2, c: 3 }
%{a: 1, b: 2, c: 3}
iex> m1 = %{ m | b: "two", c: "three" } %{a: 1, b: "two", c: "three"}
iex> m2 = %{ m1 | a: "one" }
%{a: "one", b: "two", c: "three"}

However, this syntax won’t add a new key to a map. To do this, we have to use the Map.put_new/3 function.

Try it out

Use terminal below to run the above commands.

Terminal 1
Terminal
Loading...