Skip to content

Commit f59be83

Browse files
committed
Add support for maps:merge_with/3
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent d6f74b5 commit f59be83

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
2121
- Support for `code:ensure_loaded/1`
2222
- Support for Elixir `List.Chars` protocol
2323
- Support for `lists:last/1` and `lists:mapfoldl/3`
24+
- Support for `maps:merge_with/3`
2425

2526
### Changed
2627

libs/estdlib/src/maps.erl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from_keys/2,
5656
map/2,
5757
merge/2,
58+
merge_with/3,
5859
remove/2,
5960
update/3
6061
]).
@@ -439,6 +440,28 @@ merge(Map1, _Map2) when not is_map(Map1) ->
439440
merge(_Map1, Map2) when not is_map(Map2) ->
440441
error({badmap, Map2}).
441442

443+
%%-----------------------------------------------------------------------------
444+
%% @param Combiner a function to merge values from Map1 and Map2 if a key exists in both maps
445+
%% @param Map1 a map
446+
%% @param Map2 a map
447+
%% @returns the result of merging entries from `Map1' and `Map2'.
448+
%% @doc Merge two maps to yield a new map.
449+
%%
450+
%% If `Map1' and `Map2' contain the same key, then the value from `Combiner(Key, Value1, Value2)' will be used.
451+
%%
452+
%% This function raises a `badmap' error if neither `Map1' nor `Map2' is a map.
453+
%% @end
454+
%%-----------------------------------------------------------------------------
455+
-spec merge_with(
456+
Combiner :: fun((Key, Value, Value) -> Value), Map1 :: #{Key => Value}, Map2 :: #{Key => Value}
457+
) -> #{Key => Value}.
458+
merge_with(Combiner, Map1, Map2) when is_map(Map1) andalso is_map(Map2) ->
459+
iterate_merge_with(Combiner, maps:next(maps:iterator(Map1)), Map2);
460+
merge_with(_Combiner, Map1, _Map2) when not is_map(Map1) ->
461+
error({badmap, Map1});
462+
merge_with(_Combiner, _Map1, Map2) when not is_map(Map2) ->
463+
error({badmap, Map2}).
464+
442465
%%-----------------------------------------------------------------------------
443466
%% @param Key the key to remove
444467
%% @param MapOrIterator the map or map iterator from which to remove the key
@@ -545,6 +568,19 @@ iterate_map(Fun, {Key, Value, Iterator}, Accum) ->
545568
NewAccum = Accum#{Key => Fun(Key, Value)},
546569
iterate_map(Fun, maps:next(Iterator), NewAccum).
547570

571+
%% @private
572+
iterate_merge_with(_Combiner, none, Accum) ->
573+
Accum;
574+
iterate_merge_with(Combiner, {Key, Value1, Iterator}, Accum) ->
575+
case Accum of
576+
#{Key := Value2} ->
577+
iterate_merge_with(Combiner, maps:next(Iterator), Accum#{
578+
Key := Combiner(Key, Value1, Value2)
579+
});
580+
#{} ->
581+
iterate_merge_with(Combiner, maps:next(Iterator), Accum#{Key => Value1})
582+
end.
583+
548584
%% @private
549585
iterate_merge(none, Accum) ->
550586
Accum;

tests/libs/estdlib/test_maps.erl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ test() ->
5656
ok = test_foreach(),
5757
ok = test_map(),
5858
ok = test_merge(),
59+
ok = test_merge_with(),
5960
ok = test_remove(),
6061
ok = test_update(),
6162
ok.
@@ -284,6 +285,43 @@ test_merge() ->
284285
ok = check_bad_map(fun() -> maps:merge(id(not_a_map), maps:new()) end),
285286
ok.
286287

288+
test_merge_with() ->
289+
?ASSERT_EQUALS(maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), maps:new()), #{}),
290+
?ASSERT_EQUALS(
291+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, c => 3}, maps:new()), #{
292+
a => 1, b => 2, c => 3
293+
}
294+
),
295+
?ASSERT_EQUALS(
296+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), #{a => 1, b => 2, c => 3}), #{
297+
a => 1, b => 2, c => 3
298+
}
299+
),
300+
?ASSERT_EQUALS(
301+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, d => 4}, #{
302+
a => 1, b => 2, c => 3
303+
}),
304+
#{a => 2, b => 4, c => 3, d => 4}
305+
),
306+
?ASSERT_EQUALS(
307+
maps:merge_with(fun(_K, V1, V2) -> {V1, V2} end, #{a => 1, b => 2, c => 3}, #{
308+
b => z, d => 4
309+
}),
310+
#{
311+
a => 1,
312+
b => {2, z},
313+
c => 3,
314+
d => 4
315+
}
316+
),
317+
ok = check_bad_map(fun() ->
318+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), id(not_a_map))
319+
end),
320+
ok = check_bad_map(fun() ->
321+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, id(not_a_map), maps:new())
322+
end),
323+
ok.
324+
287325
test_remove() ->
288326
?ASSERT_EQUALS(maps:remove(foo, maps:new()), #{}),
289327
?ASSERT_EQUALS(maps:remove(a, #{a => 1, b => 2, c => 3}), #{b => 2, c => 3}),

0 commit comments

Comments
 (0)