axelson
How can you assert that a map in a variable matches another map that is a superset?
Given these two maps:
map1 = %{a: 1}
map2 = %{a:1, b: 2}
This works fine:
%{a: 1} = map2
But these do not:
iex> ^map1 = map2
** (MatchError) no match of right hand side value: %{a: 1, b: 2}
iex> match?(^map1, map2)
false
Assuming I have a subset of what I want to match in a variable is there a way to use pattern matching to check that I have a subset?
Marked As Solved
peerreynders
The trivial/pedestrian (i.e. no pattern match) way:
map1 = %{a: 1}
map2 = %{a: 1, b: 2}
set1 = MapSet.new(map1)
set2 = MapSet.new(map2)
IO.puts("map1 ⊆ map2 #{MapSet.subset?(set1, set2)}")
IO.puts("map2 ⊆ map1 #{MapSet.subset?(set2, set1)}
$ elixir demo.exs
map1 ⊆ map2 true
map2 ⊆ map1 false
$
or
map1 = %{a: 1}
map2 = %{a: 1, b: 2}
map3 = %{c: 3}
subset? =
fn (map1, map2) ->
keys = Map.keys(map1)
map2
|> Map.take(keys)
|> Map.equal?(map1)
end
IO.puts("map1 ⊆ map2 #{subset?.(map1, map2)}")
IO.puts("map2 ⊆ map1 #{subset?.(map2, map1)}")
IO.puts("map3 ⊆ map2 #{subset?.(map3, map2)}")
$ elixir demo2.exs
map1 ⊆ map2 true
map2 ⊆ map1 false
map3 ⊆ map2 false
and another
map1 = %{a: 1}
map2 = %{a: 1, b: 2}
map3 = %{c: 3}
subset? =
fn (map1, map2) ->
in_other =
fn {key, value} ->
{:ok, value} == Map.fetch(map2, key)
end
map1
|> Map.to_list()
|> Enum.all?(in_other)
end
IO.puts("map1 ⊆ map2 #{subset?.(map1, map2)}")
IO.puts("map2 ⊆ map1 #{subset?.(map2, map1)}")
IO.puts("map3 ⊆ map2 #{subset?.(map3, map2)}")
$ elixir demo3.exs
map1 ⊆ map2 true
map2 ⊆ map1 false
map3 ⊆ map2 false
$
I have the sneaking suspicion that the pattern is a compile time construct - it’s the binding that happens at runtime.
Also Liked
manukall
this doesn’t do what you would think, though:
iex(4)> match?(a, %{x: 5})
true
peerreynders
My gut pick is this one:
subset? =
fn (map1, map2) ->
in_other =
fn {key, value} ->
{:ok, value} == Map.fetch(map2, key)
end
map1
|> Map.to_list()
|> Enum.all?(in_other)
end
as it simply verifies that all the necessary key/value pairs are present and will stop as soon as that is not the case.
In this case I also prefer to use fetch/2 in order the leverage the underlying map functionality rather than relying on in/2’s macro magic even if it makes the code a bit more verbose (so be it).
One thing to be aware of is that the empty set is a subset of any other set - the same is true for empty maps.
NobbZ
Do not pin here:
iex(1)> a = %{a: 1}
iex(2)> b = %{a: 1, b: 2}
iex(3)> match?(a, b)
true
kelvinst
Now I see my code is actually the same as @peerreynders second suggestion but in only one line.
If you want also, his last suggestion can be written as:
map1 = %{a: 1}
map2 = %{a: 1, b: 2}
map1
|> Map.to_list()
|> Enum.all?(&(&1 in map2))
But I still think MapSet.subset?/2 is clearer, and as you can see in its code, looks like it does pretty much the same thing.
kelvinst
Now that @axelson has a workaround for it, I want to bring a little discussion: shouldn’t his example work? I mean this one:
map1 = %{a: 1}
map2 = %{a: 1, b: 2}
^map1 = map2
I totally expected it to work. Didn’t you?
I mean, if we think logically: if %{a: 1} = b works and
a = %{a: 1}, then ^a = b should work.
Do any of you know why it doesn’t? Is there a specific reason for it?







