Skip to content

Commit

Permalink
Add support for maps:merge_with/3
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Guyot <[email protected]>
  • Loading branch information
pguyot committed Sep 23, 2024
1 parent 8b9419a commit 2b33957
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
- Add support to Elixir `Enumerable` protocol also for `Enum.all?`, `Enum.any?`, `Enum.each` and
`Enum.filter`
- Add support for `is_bitstring/1` construct which is used in Elixir protocols runtime.
- Support for `maps:merge_with/3`

### Changed

Expand Down
36 changes: 36 additions & 0 deletions libs/estdlib/src/maps.erl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from_keys/2,
map/2,
merge/2,
merge_with/3,
remove/2,
update/3
]).
Expand Down Expand Up @@ -439,6 +440,28 @@ merge(Map1, _Map2) when not is_map(Map1) ->
merge(_Map1, Map2) when not is_map(Map2) ->
error({badmap, Map2}).

%%-----------------------------------------------------------------------------
%% @param Combiner a function to merge values from Map1 and Map2 if a key exists in both maps
%% @param Map1 a map
%% @param Map2 a map
%% @returns the result of merging entries from `Map1' and `Map2'.
%% @doc Merge two maps to yield a new map.
%%
%% If `Map1' and `Map2' contain the same key, then the value from `Combiner(Key, Value1, Value2)' will be used.
%%
%% This function raises a `badmap' error if neither `Map1' nor `Map2' is a map.
%% @end
%%-----------------------------------------------------------------------------
-spec merge_with(
Combiner :: fun((Key, Value, Value) -> Value), Map1 :: #{Key => Value}, Map2 :: #{Key => Value}
) -> #{Key => Value}.
merge_with(Combiner, Map1, Map2) when is_map(Map1) andalso is_map(Map2) ->
iterate_merge_with(Combiner, maps:next(maps:iterator(Map1)), Map2);
merge_with(_Combiner, Map1, _Map2) when not is_map(Map1) ->
error({badmap, Map1});
merge_with(_Combiner, _Map1, Map2) when not is_map(Map2) ->
error({badmap, Map2}).

%%-----------------------------------------------------------------------------
%% @param Key the key to remove
%% @param MapOrIterator the map or map iterator from which to remove the key
Expand Down Expand Up @@ -545,6 +568,19 @@ iterate_map(Fun, {Key, Value, Iterator}, Accum) ->
NewAccum = Accum#{Key => Fun(Key, Value)},
iterate_map(Fun, maps:next(Iterator), NewAccum).

%% @private
iterate_merge_with(_Combiner, none, Accum) ->
Accum;
iterate_merge_with(Combiner, {Key, Value1, Iterator}, Accum) ->
case Accum of
#{Key := Value2} ->
iterate_merge_with(Combiner, maps:next(Iterator), Accum#{
Key := Combiner(Key, Value1, Value2)
});
#{} ->
iterate_merge_with(Combiner, maps:next(Iterator), Accum#{Key => Value1})
end.

%% @private
iterate_merge(none, Accum) ->
Accum;
Expand Down
38 changes: 38 additions & 0 deletions tests/libs/estdlib/test_maps.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ test() ->
ok = test_foreach(),
ok = test_map(),
ok = test_merge(),
ok = test_merge_with(),
ok = test_remove(),
ok = test_update(),
ok.
Expand Down Expand Up @@ -284,6 +285,43 @@ test_merge() ->
ok = check_bad_map(fun() -> maps:merge(id(not_a_map), maps:new()) end),
ok.

test_merge_with() ->
?ASSERT_EQUALS(maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), maps:new()), #{}),
?ASSERT_EQUALS(
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, c => 3}, maps:new()), #{
a => 1, b => 2, c => 3
}
),
?ASSERT_EQUALS(
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), #{a => 1, b => 2, c => 3}), #{
a => 1, b => 2, c => 3
}
),
?ASSERT_EQUALS(
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, d => 4}, #{
a => 1, b => 2, c => 3
}),
#{a => 2, b => 4, c => 3, d => 4}
),
?ASSERT_EQUALS(
maps:merge_with(fun(_K, V1, V2) -> {V1, V2} end, #{a => 1, b => 2, c => 3}, #{
b => z, d => 4
}),
#{
a => 1,
b => {2, z},
c => 3,
d => 4
}
),
ok = check_bad_map(fun() ->
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), id(not_a_map))
end),
ok = check_bad_map(fun() ->
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, id(not_a_map), maps:new())
end),
ok.

test_remove() ->
?ASSERT_EQUALS(maps:remove(foo, maps:new()), #{}),
?ASSERT_EQUALS(maps:remove(a, #{a => 1, b => 2, c => 3}), #{b => 2, c => 3}),
Expand Down

0 comments on commit 2b33957

Please sign in to comment.