From 82c5ff3866e3019eb347c7f1d8f1f847bed28c10 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogov Date: Mon, 27 Sep 2021 19:16:22 +0300 Subject: [PATCH] feat: Add map:zipfold (#37) --- src/genlib_map.erl | 23 ++++++++++++++++ test/prop_genlib_map.erl | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/genlib_map.erl b/src/genlib_map.erl index 48274bc..e7fc0c0 100644 --- a/src/genlib_map.erl +++ b/src/genlib_map.erl @@ -19,6 +19,7 @@ -export([diff/2]). -export([fold_while/3]). -export([search/2]). +-export([zipfold/4]). %% @@ -176,3 +177,25 @@ do_search(Fun, {Key, Value, Iter}) -> true -> {Key, Value}; false -> do_search(Fun, maps:next(Iter)) end. + +%% @doc Fold two maps "joining" them by key. +%% NOTE: If a key-value exists only in one map, this pair is ignored altogether +-spec zipfold( + fun((K, V1, V2, A) -> A), + InitAcc :: A, + #{K => V1}, + #{K => V2} +) -> A. +zipfold(Fun, Acc, M1, M2) -> + maps:fold( + fun(Key, V1, AccIn) -> + case maps:find(Key, M2) of + {ok, V2} -> + Fun(Key, V1, V2, AccIn); + error -> + AccIn + end + end, + Acc, + M1 + ). diff --git a/test/prop_genlib_map.erl b/test/prop_genlib_map.erl index e20b62e..b4613f2 100644 --- a/test/prop_genlib_map.erl +++ b/test/prop_genlib_map.erl @@ -57,6 +57,63 @@ prop_search_not_found() -> end ). +-spec prop_zipfold() -> proper:test(). +prop_zipfold() -> + ?FORALL( + [Left, Right], + [map(), map()], + begin + CommonKeys = maps_common_keys(Left, Right), + + Actual = + genlib_map:zipfold( + fun(Key, LValue, RValue, Acc) -> Acc#{Key => {LValue, RValue}} end, + #{}, + Left, + Right + ), + + Expected = + maps_merge_with( + fun(_Key, LValue, RValue) -> {LValue, RValue} end, + Left, + Right + ), + + Actual =:= maps:with(CommonKeys, Expected) + end + ). + +-if(?OTP_RELEASE >= 24). + +maps_merge_with(Combiner, Left, Right) -> + maps:merge_with(Combiner, Left, Right). + +-else. + +maps_merge_with(Combiner, Left, Right) -> + CommonMerged = + lists:foldl( + fun(Key, Acc) -> + Acc#{Key => Combiner(Key, maps:get(Key, Left), maps:get(Key, Right))} + end, + #{}, + maps_common_keys(Left, Right) + ), + + maps:merge(maps:merge(Left, Right), CommonMerged). + +%% END -if(?OTP_RELEASE >= 24). +-endif. + +maps_common_keys(LeftMap, RightMap) -> + sets:to_list( + sets:intersection( + sets:from_list(maps:keys(LeftMap)), + sets:from_list(maps:keys(RightMap)) + ) + ). + map() -> ?LET(KVList, list({term(), term()}), maps:from_list(KVList)).