Pattern Matching and Updating Maps
Learn to perform pattern matching with maps and to update them.
We'll cover the following
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
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 theheight
from that map toheight
. - The filter selects only those maps where the
height
exceeds1.5
, and thedo
block returns thepeople
that match. - The comprehension as a whole returns a list of these
people
, whichIO.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.