Skip to content

Instantly share code, notes, and snippets.

@fabjan
Last active January 31, 2023 11:50
Show Gist options
  • Save fabjan/dcdf68461926faba3a91d3ea024f6cbf to your computer and use it in GitHub Desktop.
Save fabjan/dcdf68461926faba3a91d3ea024f6cbf to your computer and use it in GitHub Desktop.
-module(mapmap).
%% Extract all key paths from a nested map, sorted.
%%
%% Example:
%%
%% > mapmap:key_paths(#{a => 1, b => #{c => 2, d => 3}}).
%% [[a], [b, c], [b, d]]
key_paths(Map) ->
lists:sort(key_paths(Map, [])).
key_paths(Map, ParentPath) when is_map(Map) ->
lists:foldl(
fun(Key, Acc) ->
ChildValue = maps:get(Key, Map),
ChildPath = ParentPath ++ [Key],
case key_paths(ChildValue, ChildPath) of
[] -> Acc;
ChildPaths -> Acc ++ ChildPaths
end
end,
[],
maps:keys(Map)
);
key_paths(_, Path) ->
[Path].
%% Compare the shape of two nested maps.
%% Returns a list of key paths that only exist in the first map, and a list of
%% key paths that only exist in the second map.
%% The values in the maps don't matter.
%%
%% Example:
%% > mapmap:key_diff(#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2}}).
%% {[[b, d]], []}
%% > mapmap:key_diff(#{a => 1, b => #{c => 2}}, #{a => 1, b => #{c => 2, d => 3}}).
%% {[], [[b, d]]}
key_diff(Map1, Map2) ->
KeysInMap1 = key_paths(Map1),
KeysInMap2 = key_paths(Map2),
{lists:usort(KeysInMap1 -- KeysInMap2), lists:usort(KeysInMap2 -- KeysInMap1)}.
%% For a list of maps, count the number of times each key path appears.
%% Returns a map of key paths to counts.
%% The values in the maps don't matter.
%%
%% Example:
%% > mapmap:key_counts([#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2}}]).
%% #{[a] => 2, [b, c] => 2, [b, d] => 1}
%% > mapmap:key_counts([#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2, d => 3}}]).
%% #{[a] => 2, [b, c] => 2, [b, d] => 2}
key_counts(Maps) ->
lists:foldl(
fun(Map, Acc) ->
lists:foldl(
fun(KeyPath, Acc2) ->
maps:update_with(KeyPath, fun(C) -> C + 1 end, 1, Acc2)
end,
Acc,
key_paths(Map)
)
end,
#{},
Maps
).
%% -------------------------------------------------------------------
%% Tests
%% -------------------------------------------------------------------
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
key_paths_test() ->
?assertEqual(
[[a], [b, c], [b, d]],
key_paths(#{a => 1, b => #{c => 2, d => 3}})
).
really_deep_path_test() ->
?assertEqual(
[[a], [b, c, d, e]],
key_paths(#{a => 1, b => #{c => #{d => #{e => 1}}}})
).
really_wide_path_test() ->
?assertEqual(
[[a], [b], [c], [d], [e], [f], [g], [h], [i], [j], [k], [l], [m], [n], [o], [p], [q], [r], [s], [t], [u], [v], [w], [x], [y], [z]],
key_paths(#{a => 1, b => 1, c => 1, d => 1, e => 1, f => 1, g => 1, h => 1, i => 1, j => 1, k => 1, l => 1, m => 1, n => 1, o => 1, p => 1, q => 1, r => 1, s => 1, t => 1, u => 1, v => 1, w => 1, x => 1, y => 1, z => 1})
).
key_diff_test() ->
?assertEqual(
{[[b, d]], []},
key_diff(#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2}})
),
?assertEqual(
{[], [[b, d]]},
key_diff(#{a => 1, b => #{c => 2}}, #{a => 1, b => #{c => 2, d => 3}})
).
longer_key_diff_test() ->
?assertEqual(
{[[b, d], [b, e]], []},
key_diff(#{a => 1, b => #{c => 2, d => 3, e => 4}}, #{a => 1, b => #{c => 2}})
),
?assertEqual(
{[], [[b, d], [b, e]]},
key_diff(#{a => 1, b => #{c => 2}}, #{a => 1, b => #{c => 2, d => 3, e => 4}})
).
key_counts_test() ->
?assertEqual(
#{[a] => 2, [b, c] => 2, [b, d] => 1},
key_counts([#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2}}])
),
?assertEqual(
#{[a] => 2, [b, c] => 2, [b, d] => 2},
key_counts([#{a => 1, b => #{c => 2, d => 3}}, #{a => 1, b => #{c => 2, d => 3}}])
).
-endif.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment