How would you merge two maps keeping only the keys of the first one

Edit : The title is a bit complex but I don’t even get how to describe this properly, so bear with me.

The thing is a python problem, which could be solved imperatively. Still, I am trying to force myself to think declaratively, and when I think experts in pattern matching, it’s here that come to mind (so I hope to get a clue when learning how to do it in elixir)

The situation is the following :

I have a first dictionnary/map (in my final case it is a DataSet from PynetDicom, but according to the documentation it is just a extended dict, so if a solution work with dict, it should do it).

This has a variable amount of field set to nil.
On the other hand I have another similar structure with all fields filed.

What I want is to fill the fields of the first with data of the second if the field name is the same.
Ex:

map1 = %{a: nil, b: nil, c:nil, d:nil}
map2 = %{c:"Hello", d:"Test", e:"Not needed"}

mapfinal = magic(map1, map2) #The thing I don't get
mapfinal = %{a:nil, b:nil, c:"Hello", d:"Test"}

In imperative, I would loop over one or both struct and if a fieldname is in the 2 of them, then struct1[“fieldname”] = struct2[“fieldname”]. But ther must be a way to do this with a tricky pattern matching.

What do you think ?

EDIT : I used the word “keyword map” which is incorrect

1 Like

Maybe simply Map.merge…

UPDATE: It won’t work because key e: will be merged too
It could work like this, but it starts to be ugly.

iex> Map.merge map1, Map.take(map2, Map.keys(map1))
%{a: nil, b: nil, c: "Hello", d: "Test"}
3 Likes

I can’t think of a nice pattern matching right now, but this code should do it:

iex(1)> map1 = %{a: nil, b: nil, c: nil, d: nil}
%{a: nil, b: nil, c: nil, d: nil}
iex(2)> map2 = %{c: "Hello", d: "Test", e: "Not needed"}
%{c: "Hello", d: "Test", e: "Not needed"}
iex(3)> Enum.reduce(map2, map1, fn {key, value}, acc -> Map.replace(acc, key, value) end)
%{a: nil, b: nil, c: "Hello", d: "Test"}
2 Likes
defmodule Foo do
  def magic(map1, map2) do
    for {k2, v2} <- map2, Map.has_key?(map1, k2), into: map1 do
      {k2, v2}
    end
  end
end
2 Likes

Another solution, since you are starting with Elixir is this:

  def magic(map1, map2) do
    keys_to_drop = Map.keys(map2) -- Map.keys(map1)
    map2_filtered = Map.drop(map2, keys_to_drop)    
    Map.merge(map1, map2_filtered)
  end
3 Likes

The title could be “How would you merge two maps keeping only the keys of the first one”

2 Likes

Thanks everyone for your input. I undestand how we could do in elixir, now I’ll try to reach a Pythonic solution. Leaving the topic open in case others have news propositions

2 Likes

Sweet! Please don’t forget to choose a solution, so the post is marked as SOLVED.

For something pretty, how about this:

def merge_keeping_only_first_keys(map_1, map_2) do
  map_1
  |> Map.merge(map_2)
  |> Map.take(Map.keys(map_1))
end

although i prefer this form:

def merge_keeping_only_first_keys(map_1, map_2) do
  map_1
  |> Map.keys()
  |> then(&Map.take(map_2, &1))
  |> then(&Map.merge(map_1, &1))
end

But I am a bit too fond of then

7 Likes

A lot of good solutions, but as I am unfairly biased and love way too much the pipe operator, this one get the cake. Still, congratulations and thanks everyone.

I should have used pipe :slight_smile:

3 Likes

Sorry @kokolegorille :sweat_smile: I didn’t look at the other answers closely. But I did search the page for Map.take before I posted my answer. I thought it had 0 matches. Maybe I had a typo in my search.

1 Like

No need to be sorry, I like your solution too :slight_smile:

@slouchpie I also like your second solution better because it is more performant than the first one. The first one merges and then takes what’s needed, while the second one takes what’s needed and then merges only that.

3 Likes