Erlang - merging maps - erlang

I am trying to figure out how to merge two maps in a way that allows me to process elements with the same keys.
For example, merging
#{"Ala" => 1,"kota" => 3}
with
#{"kota" => 4}
should result in:
#{"Ala" => 1,"kota" => 7}

There's no builtin function in Erlang that does exactly this but it can be done with maps:fold/3 and maps:update_with/4 like this:
1> A = #{"Ala" => 1,"kota" => 3}.
#{"Ala" => 1,"kota" => 3}
2> B = #{"kota" => 4}.
#{"kota" => 4}
3> maps:fold(fun(K, V, Map) -> maps:update_with(K, fun(X) -> X + V end, V, Map) end, A, B).
#{"Ala" => 1,"kota" => 7}
The code basically does this: for each item in B, if the same key exists in A, it gets the value (V) and adds the current value(X). If it doesn't exist, it sets the value to V

This function will merge two maps, running a function (Fun) where the key exists in both maps. It also handled the situation where Map1 is bigger than Map2 and vice-versa.
map_merge(Map1, Map2, Fun) ->
Size1 = maps:size(Map1),
Size2 = maps:size(Map2),
if
Size1 > Size2 ->
Folder = fun(K, V1, Map) ->
maps:update_with(K, fun(V2) -> Fun(K, V1, V2) end, V1, Map)
end,
maps:fold(Folder, Map1, Map2);
true ->
Folder = fun(K, V1, Map) ->
maps:update_with(K, fun(V2) -> Fun(K, V2, V1) end, V1, Map)
end,
maps:fold(Folder, Map2, Map1)
end.
Example usage in the erl shell:
1> my_module:map_merge(#{"a" => 10, "b" => 2}, #{"a" => 1}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 11,"b" => 2}
2> my_module:map_merge(#{"a" => 10}, #{"a" => 1, "b" => 2}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 11,"b" => 2}
3> my_module:map_merge(#{"a" => 10},#{"a" => 10}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 20}

Related

Function to convert map to a list in erlang

Suppose I have a map like this
A = #{a=>1,b=>2,c=>3}.
I want to create a function which converts A to a list of tuples of key-value pairs.
list = [{a,1},{b,2},{c,3}]
maps:to_list/1 does exactly this:
1> maps:to_list(#{a=>1,b=>2,c=>3}).
[{a,1},{b,2},{c,3}]
You can use maps:fold/3 for loop map items. Let's say you need just convert a map, then you can use something like:
1> A = #{a=>1,b=>2,c=>3}.
2> maps:fold(
fun(K, V, Acc) ->
[{K, V} | Acc]
end,
[], A).
[{c,3},{b,2},{a,1}]
For case if need to do the same for nested maps, this example can be modify like:
1> A = #{a => 1, b => 2, c => 3, d => #{a => 1, b => #{a => 1}}},
2> Nested =
fun F(K, V = #{}, Acc) -> [{K, maps:fold(F, [], V)} | Acc];
F(K, V, Acc) -> [{K, V} | Acc]
end,
3> maps:fold(Nested, [], A).
[{d,[{b,[{a,1}]},{a,1}]},{c,3},{b,2},{a,1}]

Pattern matching map with exact list of keys

In order to validate some JSON documents, I need to ensure that my JSON (represented has a map) has a specific set of fields not more, not less.
From the best of my knowledge pattern matching with something like #{a := FieldA} = MyJSON only ensures that the key a is present in MyJSON.
Unfortunately maps:with/2 ignores missing keys and most other maps functions work on a single key at the time.
In this case, what is the recommended way to ensure that a map contains a specific field or list of fields and not any other?
My current, ugly, solution is to pattern match all the keys I need and rebuild a new map like in:
validate(#{a := A, b := B, c := C}) ->
#{a => A, b => B, c => C}.
But it becomes very hard to maintain, very quickly.
if i understand your problem well, this code should do what you want:
is_map_valid(Map,SortedKeyList) ->
SortedKeyList == lists:sort(maps:keys(Map)).
tested in the shell:
1> Is_map_valid = fun(Map,SortedKeyList) -> SortedKeyList == lists:sort(maps:keys(Map)) end.
#Fun<erl_eval.12.99386804>
2> Map1 = #{a => 1, b => 2}.
#{a => 1,b => 2}
3> Map2 = #{a => 1, b => 2, c => 3}.
#{a => 1,b => 2,c => 3}
4> Map3 = #{a => 1, b => 2, c => 3, d => 4}.
#{a => 1,b => 2,c => 3,d => 4}
5> Keys = [a,b,c].
[a,b,c]
6> Is_map_valid(Map1,Keys).
false
7> Is_map_valid(Map2,Keys).
true
8> Is_map_valid(Map3,Keys).
false
9>
If I understand correctly: use maps:size (or erlang:map_size if you need it in a guard condition) after maps:with, i.e. something like
validate(Keys, Map) ->
ExpectedSize = erlang:length(Keys),
case maps:with(Keys, Map) of
Map1 when erlang:map_size(Map1) == ExpectedSize -> {ok, Map1};
_ -> invalid
end.
You can also check Map size first to fail quickly.

Replace key in nested maps

How can i replace all keys in the nested map with another key without knowing the path (or replace keys, that match a pattern), for example:
Map = #{foo => #{bar => 1, foo => #{bar => 2}}}.
Map2 = maps:keyreplace(bar, foo, Map).
>>> #{foo => #{foo => 1, foo => #{foo => 2}}}
What is the most efficient way to do this?
Replacing key in a flat map seems to be pretty simple:
keyreplace(K1, K2, M) when is_map(M) ->
case maps:take(K1, M) of
{V, M2} ->
maps:put(K2, V, M2);
error -> M
end.
Or like that, maybe:
keymap(F, Map) ->
maps:fold(fun(K, V, Acc) ->
maps:put(F(K), V, Acc)
end, #{}, Map).
Map = #{foo => 1, bar => 2}.
Fun = fun(K) -> atom_to_list(K) end.
Map2 = keymap(Fun, Map).
>>> #{"bar" => 2,"foo" => 1}
I wrote recursive function which you can use to achieve your goal, it takes 3 arguments:
Is_matched - function in which you check that key match your pattern.
Convert - function which converts key as you want.
Map - map to process.
replace_keys(Is_matched, Convert, Map) when is_map(Map) ->
maps:fold(fun (K, V, AccIn) ->
NewKey =
case Is_matched(K) of
true -> Convert(K);
false -> K
end,
maps:put(NewKey, replace_keys(Is_matched, Convert, V), AccIn)
end, #{}, Map);
replace_keys(_, _, Term) -> Term.
Example:
1> Map = #{a => "1", <<"b">> => #{c => "2", <<"d">> => 3}}.
#{a => "1",<<"b">> => #{c => "2",<<"d">> => 3}}
2> Is_matched = fun(Term) -> is_atom(Term) end.
#Fun<erl_eval.6.99386804>
3> Convert = fun(Term) -> atom_to_list(Term) end.
#Fun<erl_eval.6.99386804>
4> keyreplace:replace_keys(Is_matched, Convert, Map).
#{"a" => "1",<<"b">> => #{"c" => "2",<<"d">> => 3}}
This can be done with some modifications to your maps:fold attempt:
If the third argument is not a map, return it as is.
When folding:
Recursively call the function with the value field to obtain the new value.
If the key matches K1, put the new value into K2.
Your example output is incorrect - it has 2 values for the foo key. I've modified it in the test below.
keyreplace(K1, K2, Map) when is_map(Map) ->
maps:fold(fun(K, V, Acc) ->
Key = if K == K1 -> K2; true -> K end,
Value = keyreplace(K1, K2, V),
maps:put(Key, Value, Acc)
end, #{}, Map);
keyreplace(_K1, _K2, Term) -> Term.
1> c(a).
{ok,a}
2> Map = #{foo => #{bar => 1, baz => #{bar => 2}}}.
#{foo => #{bar => 1,baz => #{bar => 2}}}
3> a:keyreplace(bar, foo, Map).
#{foo => #{baz => #{foo => 2},foo => 1}}

return first {Key, Value} from Map where Predicate is true

The more formal definition of problem is
Write a function map_search_pred(Map, Pred) that returns the first
element {Key,Value} in the map for which Pred(Key, Value) is true.
My attempt, looks like
map_search_pred(Map, Pred) ->
M = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
case length(M) of
0 -> {};
_ -> lists:nth(1, M)
end.
When I run this, I get correct answer
1> lib_misc:map_search_pred(#{}, fun(X, Y) -> X =:= Y end).
{}
2> lib_misc:map_search_pred(#{1 => 1, 2 => 2}, fun(X, Y) -> X =:= Y end).
{1,1}
3> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{1,1}
4> lib_misc:map_search_pred(#{1 => 2, 2 => 2}, fun(X, Y) -> X =:= Y end).
{2,2}
5>
Problem?
The problem is efficiency.
It uses list comprehension and runs for all the elements in the list. The better solution would be to return after the first match.
As a beginner to language, I do not know a better idiomatic way to solve this, so looking for better ideas and suggestions
You could walk the list yourself and return as soon as you find an element for which Pred(Key, Value) is true:
map_search_pred(Map, Pred) when is_map(Map) ->
map_search_pred(maps:to_list(Map), Pred);
map_search_pred([], _) ->
error;
map_search_pred([{Key,Value}=H|T], Pred) ->
case Pred(Key, Value) of
true ->
{ok, H};
false ->
map_search_pred(T, Pred)
end.

Empty Map Pattern matches even for non-empty Map

The problem I am trying to solve states
Write a function map_search_pred(Map, Pred) that returns the first
element {Key,Value} in the map for which Pred(Key, Value) is true.
My attempt looks like
map_search_pred(#{}, _) -> {};
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.
When I run this, I see output as
1> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{}
2> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{}
3> maps:size(#{}).
0
4>
How am I so sure?
I pulled out the first clause so it looks like
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.
and run again
1> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{1,1}
2> lib_misc:map_search_pred(#{}, fun(X, Y) -> X =:= Y end).
** exception error: no match of right hand side value []
in function lib_misc:map_search_pred/2 (/Users/harith/code/IdeaProjects/others/erlang/programmingErlang/src/lib_misc.erl, line 42)
3>
According to map documentation:
Matching an expression against an empty map literal will match its type but no variables will be bound:
#{} = Expr
This expression will match if the expression Expr is of type map, otherwise it will fail with an exception badmatch.
However erlang:map_size can be used instead:
map_search_pred(Map, _) when map_size(Map) == 0 ->
{};
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.

Resources