/home/runner/work/klsn/klsn/_build/test/cover/aggregate/klsn_map.html

1 -module(klsn_map).
2
3 -export([
4 lookup/2
5 , get/2
6 , get/3
7 , upsert/3
8 , remove/2
9 , filter/1
10 , invert/1
11 ]).
12
13 -export_type([
14 key/0
15 ]).
16
17 %% A path into a nested map, represented as a list of keys. Example:
18 %% [foo, bar, baz] first looks up foo in the outer map, then bar in
19 %% the returned map, and finally baz.
20 -type key() :: [term()].
21
22
23 %% @doc
24 %% Fetch the value at Key inside Map or raise error:not_found when
25 %% the path cannot be resolved.
26 -spec get(key(), map()) -> term().
27 get(Key, Map) ->
28 4 case lookup(Key, Map) of
29 {value, Value} ->
30 2 Value;
31 none ->
32 2 erlang:error(not_found, [Key, Map])
33 end.
34
35 %% @doc
36 %% Same as get/2 but returns Default instead of throwing.
37 -spec get(key(), map(), term()) -> term().
38 get(Key, Map, Default) ->
39 4 klsn_maybe:get_value(lookup(Key, Map), Default).
40
41 %% @doc
42 %% Variant of get/2 that never throws. Returns {value, V} when the
43 %% path exists or none otherwise.
44 -spec lookup(key(), map()) -> klsn:'maybe'(term()).
45 lookup([], Value) ->
46 17 {value, Value};
47 lookup(_, Value) when not is_map(Value) ->
48 4 none;
49 lookup([H|T], Map) ->
50 34 case maps:find(H, Map) of
51 {ok, Value} ->
52 24 lookup(T, Value);
53 error ->
54 10 none
55 end.
56
57 %% @doc
58 %% Insert or replace the element located at *Key* with *Value* inside
59 %% *Map*. Missing intermediary maps are created on-the-fly.
60 -spec upsert(key(), term(), map()) -> map().
61 upsert(Key, Value, Map) ->
62 11 upsert_(Key, Value, {value, Map}, [], []).
63 upsert_([], Value, _, [], []) ->
64 9 Value;
65 upsert_([], Value, _, [{value, Map}|Maps], [Key|Keys]) ->
66 10 upsert_([], Map#{Key=>Value}, none, Maps, Keys);
67 upsert_([], Value, _, [none|Maps], [Key|Keys]) ->
68 1 upsert_([], #{Key=>Value}, none, Maps, Keys);
69 upsert_([H|T], Value, none, Maps, Keys) ->
70 1 upsert_(T, Value, none, [none|Maps], [H|Keys]);
71 upsert_([H|T], Value, {value, Map}, Maps, Keys) ->
72 11 Elem = lookup([H], Map),
73 11 upsert_(T, Value, Elem, [{value, Map}|Maps], [H|Keys]).
74
75
76 %% @doc
77 %% Remove the element at *Path* inside *Map*. Returns Map unchanged when the
78 %% path cannot be resolved. An empty path clears the map.
79 -spec remove(key(), map()) -> map().
80 remove([], _Map) ->
81 1 #{};
82 remove(Path, Map) ->
83 5 [KeyToDelete|PathToKeepRev] = lists:reverse(Path),
84 5 PathToKeep = lists:reverse(PathToKeepRev),
85 5 case lookup(PathToKeep, Map) of
86 {value, MapToDelete} when is_map(MapToDelete) ->
87 3 upsert(PathToKeep, maps:remove(KeyToDelete, MapToDelete), Map);
88 _ ->
89 2 Map
90 end.
91
92
93 %% @doc
94 %% Remove entries whose value is none and unwrap {value, V}.
95 -spec filter(maps:map(Key, klsn:'maybe'(Value))) -> maps:map(Key, Value).
96 filter(Map) ->
97 5 maps:filtermap(fun
98 (_, {value, Value}) ->
99 3 {true, Value};
100 (_, none) ->
101 4 false
102 end, Map).
103
104 %% @doc
105 %% Produce a new map where keys become values and vice-versa.
106 -spec invert(maps:map(Key, Value)) -> maps:map(Value, Key).
107 invert(Map) ->
108 2 maps:from_list(lists:map(fun({Key, Value})->
109 2 {Value, Key}
110 end, maps:to_list(Map))).
Line Hits Source