# Map.intersection/2?

Has anyone else ever wanted to merge two maps, but have the resulting map only include keys common to both maps? I think of it as analogous to MapSet.intersection but with a function for handling the values.

``````m1 = %{a: 1, b:2}
m2 = %{b: 3, c:4}
Map.intersection(m1, m2, fn _k, v1, v2 -> v1 * v2 end)
> %{b: 6}
``````

This would replace

``````Map.merge(m1, m2, fn _k , v1, v2 -> v1 * v2 end)
|> Enum.filter(fn {k,_v} -> [m1, m2] |> Enum.all?(&Map.has_key?(&1, k)) end)
|> Map.new()
>%{b: 6}
``````

Thoughts?

1 Like

All you have to do is a simple reducer.

``````defmodule Example do
@spec sample(map(), map(), (Map.key(), Map.value(), Map.value() -> Map.value())) :: map()
def sample(left, right, func) when is_map(left) and is_map(right) and is_function(func, 3) do
Enum.reduce(left, %{}, fn
{key, value}, acc when is_map_key(right, key) ->
Map.put(acc, key, func.(key, value, right[key]))

_pair, acc ->
acc
end)
end
end

iex> Example.sample(%{a: 1, b: 2}, %{b: 3, c: 4}, fn _k, v1, v2 -> v1 * v2 end)
%{b: 6}
``````

2 Likes

I think it should be called `merge_intersection` or something like that? Union means anything in either of the things.

2 Likes

Of course. Intersection not union. Brain fart

You can do `MapSet.intersection` on the keys of both maps and then `Map.take` with the result of it.

But I believe @Eiji’s solution will be more performant.

``````def merge_intersection(map1, map2, fun) do
for key <- Map.keys(map1) -- Map.keys(map1) -- Map.keys(map2), into: %{} do
{key, fun.(map1[key], map2[key])}
end
end
``````

Sorry, I just hit the wrong “reply” button

``````:maps.intersect(m1, m2)
%{b: 3}

:maps.intersect_with(fn _k , v1, v2 -> v1 * v2 end, m1, m2)
%{b: 6}
``````
8 Likes

I’m assuming that since Elixir’s maps preceded Erlang’s the `:maps` module is not simply wrapped by `Map` which is why there is no `Map.intersect` or `Map.intersect_with`. Are Elixir maps completely interoperable with the Erlang `:maps` implementation?

There is only ONE type of map in the BEAM. The maps in Elixir are exactly the same maps as in Erlang. Seeing Erlang is the base language on the BEAM Elixir could not have had maps before the BEAM and Erlang implemented them.

``````iex(1)> m1 = %{a: 1, b: 1, c: 1}
%{a: 1, b: 1, c: 1}
iex(2)> m2 = %{a: 2 , c: 2, d: 2}
%{a: 2, c: 2, d: 2}
iex(3)> mi = :maps.intersect(m1, m2)
%{a: 2, c: 2}
iex(4)> :io.write(m1)
#{a => 1,b => 1,c => 1}:ok
iex(5)> :io.write(m2)
#{a => 2,c => 2,d => 2}:ok
iex(6)> :io.write(m1)
#{a => 1,b => 1,c => 1}:ok
``````

The `:io.write` function is an output function in the Erlang `io` module and prints the maps which shows they are the same maps as in Erlang.

The `:maps.intersect/2` function first came in OTP 24.0 so it may not be used yet in the `Map` module.

6 Likes

Thanks for clarifying. I was thinking of MapSets.

Elixir main requires Erlang/OTP 24. Therefore if someone wants to submit a PR that adds `intersect/2` and `intersect/3`, it will be welcome.

7 Likes

Just in case anybody has ideas:

3 Likes

Or maybe HashDict — Elixir v1.14.2?

No just misinterpreting from

https://elixirforum.com/t/native-elixir-module-for-ets/53069/21

So `:sets` existed before `MapSet` but `MapSet` did not build on `:sets`, but `Map` always build on `:maps`?

1 Like

Yes.

MapSet did not build on `:sets` because `:sets` were not backed by maps but now they are.

1 Like