Skip to content

Commit 8d34b65

Browse files
committed
Merge pull request #1275 from pguyot/w38/add-maps-merge_with
Add support for `maps:merge_with/3` These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents f5cab4a + ab4272b commit 8d34b65

File tree

3 files changed

+87
-0
lines changed

3 files changed

+87
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
1818
- Add support for `is_bitstring/1` construct which is used in Elixir protocols runtime.
1919
- Add support to Elixir `Enumerable` protocol also for `Enum.all?`, `Enum.any?`, `Enum.each`,
2020
`Enum.filter`, `Enum.flat_map`, `Enum.reject`, `Enum.chunk_by` and `Enum.chunk_while`
21+
- Support for `maps:merge_with/3`
2122

2223
### Changed
2324

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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ test() ->
5656
ok = test_foreach(),
5757
ok = test_map(),
5858
ok = test_merge(),
59+
HasMergeWith =
60+
case erlang:system_info(machine) of
61+
"BEAM" ->
62+
erlang:system_info(version) >= "12.3";
63+
"ATOM" ->
64+
true
65+
end,
66+
case HasMergeWith of
67+
true ->
68+
ok = test_merge_with();
69+
false ->
70+
ok
71+
end,
5972
ok = test_remove(),
6073
ok = test_update(),
6174
ok.
@@ -284,6 +297,43 @@ test_merge() ->
284297
ok = check_bad_map(fun() -> maps:merge(id(not_a_map), maps:new()) end),
285298
ok.
286299

300+
test_merge_with() ->
301+
?ASSERT_EQUALS(maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), maps:new()), #{}),
302+
?ASSERT_EQUALS(
303+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, c => 3}, maps:new()), #{
304+
a => 1, b => 2, c => 3
305+
}
306+
),
307+
?ASSERT_EQUALS(
308+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), #{a => 1, b => 2, c => 3}), #{
309+
a => 1, b => 2, c => 3
310+
}
311+
),
312+
?ASSERT_EQUALS(
313+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, #{a => 1, b => 2, d => 4}, #{
314+
a => 1, b => 2, c => 3
315+
}),
316+
#{a => 2, b => 4, c => 3, d => 4}
317+
),
318+
?ASSERT_EQUALS(
319+
maps:merge_with(fun(_K, V1, V2) -> {V1, V2} end, #{a => 1, b => 2, c => 3}, #{
320+
b => z, d => 4
321+
}),
322+
#{
323+
a => 1,
324+
b => {2, z},
325+
c => 3,
326+
d => 4
327+
}
328+
),
329+
ok = check_bad_map(fun() ->
330+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, maps:new(), id(not_a_map))
331+
end),
332+
ok = check_bad_map(fun() ->
333+
maps:merge_with(fun(_K, V1, V2) -> V1 + V2 end, id(not_a_map), maps:new())
334+
end),
335+
ok.
336+
287337
test_remove() ->
288338
?ASSERT_EQUALS(maps:remove(foo, maps:new()), #{}),
289339
?ASSERT_EQUALS(maps:remove(a, #{a => 1, b => 2, c => 3}), #{b => 2, c => 3}),

0 commit comments

Comments
 (0)