In order to validate some JSON documents, I need to ensure that my JSON (represented has a map) has a specific set of fields not more, not less.
From the best of my knowledge pattern matching with something like #{a := FieldA} = MyJSON only ensures that the key a is present in MyJSON.
Unfortunately maps:with/2 ignores missing keys and most other maps functions work on a single key at the time.
In this case, what is the recommended way to ensure that a map contains a specific field or list of fields and not any other?
My current, ugly, solution is to pattern match all the keys I need and rebuild a new map like in:
validate(#{a := A, b := B, c := C}) ->
#{a => A, b => B, c => C}.
But it becomes very hard to maintain, very quickly.
if i understand your problem well, this code should do what you want:
is_map_valid(Map,SortedKeyList) ->
SortedKeyList == lists:sort(maps:keys(Map)).
tested in the shell:
1> Is_map_valid = fun(Map,SortedKeyList) -> SortedKeyList == lists:sort(maps:keys(Map)) end.
#Fun<erl_eval.12.99386804>
2> Map1 = #{a => 1, b => 2}.
#{a => 1,b => 2}
3> Map2 = #{a => 1, b => 2, c => 3}.
#{a => 1,b => 2,c => 3}
4> Map3 = #{a => 1, b => 2, c => 3, d => 4}.
#{a => 1,b => 2,c => 3,d => 4}
5> Keys = [a,b,c].
[a,b,c]
6> Is_map_valid(Map1,Keys).
false
7> Is_map_valid(Map2,Keys).
true
8> Is_map_valid(Map3,Keys).
false
9>
If I understand correctly: use maps:size (or erlang:map_size if you need it in a guard condition) after maps:with, i.e. something like
validate(Keys, Map) ->
ExpectedSize = erlang:length(Keys),
case maps:with(Keys, Map) of
Map1 when erlang:map_size(Map1) == ExpectedSize -> {ok, Map1};
_ -> invalid
end.
You can also check Map size first to fail quickly.
Related
Suppose I have a map like this
A = #{a=>1,b=>2,c=>3}.
I want to create a function which converts A to a list of tuples of key-value pairs.
list = [{a,1},{b,2},{c,3}]
maps:to_list/1 does exactly this:
1> maps:to_list(#{a=>1,b=>2,c=>3}).
[{a,1},{b,2},{c,3}]
You can use maps:fold/3 for loop map items. Let's say you need just convert a map, then you can use something like:
1> A = #{a=>1,b=>2,c=>3}.
2> maps:fold(
fun(K, V, Acc) ->
[{K, V} | Acc]
end,
[], A).
[{c,3},{b,2},{a,1}]
For case if need to do the same for nested maps, this example can be modify like:
1> A = #{a => 1, b => 2, c => 3, d => #{a => 1, b => #{a => 1}}},
2> Nested =
fun F(K, V = #{}, Acc) -> [{K, maps:fold(F, [], V)} | Acc];
F(K, V, Acc) -> [{K, V} | Acc]
end,
3> maps:fold(Nested, [], A).
[{d,[{b,[{a,1}]},{a,1}]},{c,3},{b,2},{a,1}]
I tried the code below , whereas the M1 is the Map consists of M1= #{a =>"Apple",b =>"Ball"}, and Str is the given by user ex: fun("ab").
I want to print the relevant value of the key in Map M1 based on the given string Str.
Tried Code:
fun(Str) ->
X = [ [X] || X <- Str],
Key = maps:keys(M1),
mOrse_convert(X,Key)
end;
mOrse_convert([],Key) ->
true;
mOrse_convert([First|Rest],Key) ->
case Key of
#{ X := A} -> A
end
mOrse_convert(Rest,Key)
end.
Can anyone help/suggest me ?
This function has 2 arguments: Str - value to find and target Map. The function returns list of all keys in Map that have value equals Str.
-module(ex).
-export([func/2]).
func(Str, Map) ->
Fun = fun(K, V, Acc) ->
if V == Str -> [K | Acc];
true -> Acc
end
end,
maps:fold(Fun, [], Map).
Go to Erlang shell and type following command:
1> c("ex.erl").
{ok,ex}
2> ex:func("a", #{1 => "a", 2 => "b", 3 => "ab"}).
[1]
3> ex:func("a", #{1 => "a", 2 => "b", 3 => "ab", 4 => "a"}).
[4,1]
Thank you Alexei , the solution provided by you is working good for an alphabet and got the output only for single character and given below, but I want to pass the List as input in as Str , I tried the below code as List as input,
Output for single alphabet :
20> c(morse).
{ok,morse}
21> morse:morse_convert("s").
Single ok
My Required output is :
20> c(morse).
{ok,morse}
21> morse:morse_convert("abc").
Apple Ball Cat ok
Tried Code :
X = [ [X] || X <- Str],
Y = func(X,Morse),
io:format(Y ).
func([],{}) -> io:fwrite("\nCompleted.\n");
func([First | Last], Morse) ->
Fun = fun(K, V, Acc) ->
if K == First -> [V | Acc];
true -> Acc
end
end,
maps:fold(Fun, [], Morse),
func(Last,Morse).
-module(a).
-compile(export_all).
cypher() ->
#{"a" =>"Apple",
"b" => "Ball",
"c" => "Cat"}.
convert([]) -> io:format("~n");
convert([Int|Ints]) ->
Word = maps:get([Int], cypher()),
io:format("~s ", [Word]),
convert(Ints).
In the shell:
~/erlang_programs$ erl
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.3 (abort with ^G)
1> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
2> a:convert("abc").
Apple Ball Cat
ok
3>
An erlang string, e.g. "abc", is actually a shortcut for creating a list of integers, where each integer is the ascii code of a letter in the string. For example:
3> "abc" =:= [97,98,99].
true
When the shell outputs a string, the shell expects you to know that the string is really a list of integers, which is confusing. To eliminate that confusion, you can tell the shell not to output strings by executing the function shell:strings(false):
5> "abc".
"abc"
6> shell:strings(false).
true
7> "abc".
[97,98,99]
Then you can see what you are really dealing with.
Next, when you use pattern matching to remove an integer from a list, you have an integer--not a string. To create a string from an integer, you need to put the integer into a list:
[Int]
You can see that here:
2> 97 =:= "a".
false
3> [97] =:= "a".
true
It's not clear whether you have atoms or strings as the keys in your map or whether you don't care. If your map must have atoms as the keys, you can convert strings to atoms using list_to_atom/1:
8> list_to_atom([97]).
a
Finally, when you want to output a string, e.g. one of the values in the map, rather than a list (after executing shell:strings(false)), you can use the ~s formatting sequence:
16> io:format("~s~n", ["Apple"]).
Apple
ok
Using a list comprehension would look like this:
convert(Str) ->
Cypher = #{"a" =>"Apple",
"b" => "Ball",
"c" => "Cat"
},
[
io:format("~s ", [maps:get([Int], Cypher)] )
|| Int <- Str
],
io:format("~n").
A list comprehension always returns a list, in this case [ok, ok, ok], which is not something you care about--you only care about the side affect of outputting the values. So, you can ignore the list and return something else, i.e. whatever io:format/1 returns, which is ok.
Remember, it's less confusing to have the shell output lists rather than strings, then only output strings when you specifically tell the shell to do it.
I was not sure to understand what you are looking for, here are 2 interpretations.
1> M = #{a =>"Apple",b =>"Ball",c=>"Corn",d=>"Donut",e=>"Ball"}.
#{a => "Apple",b => "Ball",c => "Corn",d => "Donut",
e => "Ball"}
2> L = ["Ball","Donut"].
["Ball","Donut"]
3> % first, get list of keys from list of values: expected result = [b,d,e]
3> [K || {K,V} <- maps:to_list(M), lists:member(V,L) == true].
[b,d,e]
4> L1 = "ace".
"ace"
5> % second, get list of values from list of keys: expected result ["Apple","Corn","Ball"]
5> [maps:get(list_to_atom([X]),M) || X <- L1].
["Apple","Corn","Ball"]
6>
How can i replace all keys in the nested map with another key without knowing the path (or replace keys, that match a pattern), for example:
Map = #{foo => #{bar => 1, foo => #{bar => 2}}}.
Map2 = maps:keyreplace(bar, foo, Map).
>>> #{foo => #{foo => 1, foo => #{foo => 2}}}
What is the most efficient way to do this?
Replacing key in a flat map seems to be pretty simple:
keyreplace(K1, K2, M) when is_map(M) ->
case maps:take(K1, M) of
{V, M2} ->
maps:put(K2, V, M2);
error -> M
end.
Or like that, maybe:
keymap(F, Map) ->
maps:fold(fun(K, V, Acc) ->
maps:put(F(K), V, Acc)
end, #{}, Map).
Map = #{foo => 1, bar => 2}.
Fun = fun(K) -> atom_to_list(K) end.
Map2 = keymap(Fun, Map).
>>> #{"bar" => 2,"foo" => 1}
I wrote recursive function which you can use to achieve your goal, it takes 3 arguments:
Is_matched - function in which you check that key match your pattern.
Convert - function which converts key as you want.
Map - map to process.
replace_keys(Is_matched, Convert, Map) when is_map(Map) ->
maps:fold(fun (K, V, AccIn) ->
NewKey =
case Is_matched(K) of
true -> Convert(K);
false -> K
end,
maps:put(NewKey, replace_keys(Is_matched, Convert, V), AccIn)
end, #{}, Map);
replace_keys(_, _, Term) -> Term.
Example:
1> Map = #{a => "1", <<"b">> => #{c => "2", <<"d">> => 3}}.
#{a => "1",<<"b">> => #{c => "2",<<"d">> => 3}}
2> Is_matched = fun(Term) -> is_atom(Term) end.
#Fun<erl_eval.6.99386804>
3> Convert = fun(Term) -> atom_to_list(Term) end.
#Fun<erl_eval.6.99386804>
4> keyreplace:replace_keys(Is_matched, Convert, Map).
#{"a" => "1",<<"b">> => #{"c" => "2",<<"d">> => 3}}
This can be done with some modifications to your maps:fold attempt:
If the third argument is not a map, return it as is.
When folding:
Recursively call the function with the value field to obtain the new value.
If the key matches K1, put the new value into K2.
Your example output is incorrect - it has 2 values for the foo key. I've modified it in the test below.
keyreplace(K1, K2, Map) when is_map(Map) ->
maps:fold(fun(K, V, Acc) ->
Key = if K == K1 -> K2; true -> K end,
Value = keyreplace(K1, K2, V),
maps:put(Key, Value, Acc)
end, #{}, Map);
keyreplace(_K1, _K2, Term) -> Term.
1> c(a).
{ok,a}
2> Map = #{foo => #{bar => 1, baz => #{bar => 2}}}.
#{foo => #{bar => 1,baz => #{bar => 2}}}
3> a:keyreplace(bar, foo, Map).
#{foo => #{baz => #{foo => 2},foo => 1}}
I am trying to figure out how to merge two maps in a way that allows me to process elements with the same keys.
For example, merging
#{"Ala" => 1,"kota" => 3}
with
#{"kota" => 4}
should result in:
#{"Ala" => 1,"kota" => 7}
There's no builtin function in Erlang that does exactly this but it can be done with maps:fold/3 and maps:update_with/4 like this:
1> A = #{"Ala" => 1,"kota" => 3}.
#{"Ala" => 1,"kota" => 3}
2> B = #{"kota" => 4}.
#{"kota" => 4}
3> maps:fold(fun(K, V, Map) -> maps:update_with(K, fun(X) -> X + V end, V, Map) end, A, B).
#{"Ala" => 1,"kota" => 7}
The code basically does this: for each item in B, if the same key exists in A, it gets the value (V) and adds the current value(X). If it doesn't exist, it sets the value to V
This function will merge two maps, running a function (Fun) where the key exists in both maps. It also handled the situation where Map1 is bigger than Map2 and vice-versa.
map_merge(Map1, Map2, Fun) ->
Size1 = maps:size(Map1),
Size2 = maps:size(Map2),
if
Size1 > Size2 ->
Folder = fun(K, V1, Map) ->
maps:update_with(K, fun(V2) -> Fun(K, V1, V2) end, V1, Map)
end,
maps:fold(Folder, Map1, Map2);
true ->
Folder = fun(K, V1, Map) ->
maps:update_with(K, fun(V2) -> Fun(K, V2, V1) end, V1, Map)
end,
maps:fold(Folder, Map2, Map1)
end.
Example usage in the erl shell:
1> my_module:map_merge(#{"a" => 10, "b" => 2}, #{"a" => 1}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 11,"b" => 2}
2> my_module:map_merge(#{"a" => 10}, #{"a" => 1, "b" => 2}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 11,"b" => 2}
3> my_module:map_merge(#{"a" => 10},#{"a" => 10}, fun(K, V1, V2) -> V1 + V2 end).
#{"a" => 20}
The problem I am trying to solve states
Write a function map_search_pred(Map, Pred) that returns the first
element {Key,Value} in the map for which Pred(Key, Value) is true.
My attempt looks like
map_search_pred(#{}, _) -> {};
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.
When I run this, I see output as
1> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{}
2> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{}
3> maps:size(#{}).
0
4>
How am I so sure?
I pulled out the first clause so it looks like
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.
and run again
1> lib_misc:map_search_pred(#{1 => 1, 2 => 3}, fun(X, Y) -> X =:= Y end).
{1,1}
2> lib_misc:map_search_pred(#{}, fun(X, Y) -> X =:= Y end).
** exception error: no match of right hand side value []
in function lib_misc:map_search_pred/2 (/Users/harith/code/IdeaProjects/others/erlang/programmingErlang/src/lib_misc.erl, line 42)
3>
According to map documentation:
Matching an expression against an empty map literal will match its type but no variables will be bound:
#{} = Expr
This expression will match if the expression Expr is of type map, otherwise it will fail with an exception badmatch.
However erlang:map_size can be used instead:
map_search_pred(Map, _) when map_size(Map) == 0 ->
{};
map_search_pred(Map, Pred) ->
[H|_] = [{Key, Value} || {Key, Value} <- maps:to_list(Map), Pred(Key, Value) =:= true],
H.