Function string => record member in erlang - erlang

I'm wondering how can i define function, which as an argument takes a string and returns a member of the record.
For example with the record
-record(measurement, { temperature, pm2p5, pm10, pressure, humidity, others=[]}).
And the fragment of my function:
update_measurement(Measurement, Type_as_String, Value) ->
Measurement#measurement{get_type(Type_as_String) = Value}
I want to update a value by passing a type as string, and I don't have an idea to define the function get_type(Type_as_String).
I've tried with atoms, but it didn't work.
Something like
update_measurement(Measurement, Type_as_String, Value) ->
case Type_as_String of
"temperature" -> Measurement#measurement{temperature = Value};
"humidity" -> Measurement#measurement{humidity = Value};
...
isn't ok, because i want to reuse this pattern in other functions.

If performance is not your biggest concern:
update_measurement(Measurement, Type_as_String, Value) ->
update_field(Measurement, Type_as_String, Value).
update_field(#measurement{} = Record, SKey, Value) ->
update_field(Record, SKey, Value, record_info(fields, measurement));
% add other records here
update_field(_, _, _) -> error(bad_record).
update_field(Record, SKey, Value, Fields) ->
update_field(Record, list_to_existing_atom(SKey), Value, Fields, 2).
update_field(Record, Key, Value, [Key|_], N) ->
setelement(N, Record, Value);
update_field(Record, Key, Value, [_|Fields], N) ->
update_field(Record, Key, Value, Fields, N+1);
update_field(_, _, _, [], _) ->
error(bad_key).
Note record_info/2 is not a real function but you have to provide measurement as a compile time constant.

Related

Encode optional string in Elm

I would like to encode a Maybe String to string if it has a concrete value, or null if it's Nothing.
At the moment, I use a helper function encodeOptionalString myStr to get the desired effect. I was wondering if there's a more Elm-like way of doing this. I really like the API of elm-json-decode-pipeline that allows me to write Decode.nullable Decode.string for decoding.
encodeOptionalString : Maybe String -> Encode.Value
encodeOptionalString s =
case s of
Just s_ ->
Encode.string s_
Nothing ->
Encode.null
You could generalize this into an encodeNullable function yourself:
encodeNullable : (value -> Encode.Value) -> Maybe value -> Encode.Value
encodeNullable valueEncoder maybeValue =
case maybeValue of
Just value ->
valueEncoder value
Nothing ->
Encode.null
Or if you want a slightly shorter ad hoc expression:
maybeString
|> Maybe.map Encode.string
|> Maybe.withDefault Encode.null
The package elm-community/json-extra has exactly the method you desire.
maybe : (a -> Value) -> Maybe a -> Value
Encode a Maybe value. If the value is Nothing it will be encoded as null

how to check if a string is a composition of another string?

I would like to know how check if a string is a composition of another in the case bellow :
isIn("xy","xyxy") -> true
isIn("xy","xyxyx") -> true
isIn("xy","xyxyy") -> false
I have done this :
isIn(X,Y) ->
case string:substr(Y,1,length(X)) == X of
true -> true;
false -> false
end
but I dont know how to continue checking the rest of the string .
Thank you
One approach is to "rotate" the substring while working through the string you're checking, comparing character by character:
is_in(_, []) -> true;
is_in([H|T1], [H|T2]) -> is_in(T1++[H], T2);
is_in(_,_) -> false.
The first clause of is_in/2 states that if we've checked the entire second argument such that it's now the empty string, then the answer is true. The second clause verifies via pattern matching that the heads of the two strings are identical, and if so, calls recursively with the head of the first string rotated to its tail and the head of the second string dropped. The final clause of is_in/2 returns false to handle the case of the heads of the strings not matching.
There are likely additional checks you need on the initial arguments, such as ensuring the length of the first string is less than or equal to that of the second string, ensuring that neither string is empty, etc.
There is better performant solution
is_in(S1, S2) ->
is_in(S1, false, S1, S2).
is_in(S1, A, [H|T1], [H|T2]) ->
is_in(S1, A, T1, T2);
is_in([_|_] = S1, _, [], S2) ->
is_in(S1, true, S1, S2);
is_in(_, A, _, []) -> A;
is_in(_, _, _, _) -> false.

Erlang filter list using lists:keyfind partial string

I am almost new with Erlang
I have a list as:
List = [[{name, <<"redCar1">>}, {turbo, true}], [{name, <<"redCar2">>}, {turbo, true}], [{name, <<"greenCard">>}, {turbo, false}]].
Now I want to filter all "red" Cars
I tried using:
filterCar() ->
MyF = fun(List) ->
case lists:keyfind(name, 1, List) of
{name, <<"red", _Rest/binary>>} ->
true:
_ ->
false
end
end,
MyF.
Then
lists:filter(MyF, List),
It works perfectly.
Now I want to create an generic function to filter, like:
myfilter(Value, List) ->
case lists:keyfind(name, 1, List) of
{name, <<Value, _Rest/binary>>} ->
true;
_ ->
false
end.
But when I try to execute this function I got always [] empty list.
I am sure the problem is when I try to pass Value because if I replace
{name, <<Value, _Rest/binary>>}
with
{name, <<"red", _Rest/binary>>}
It works.
My aim it to find all string that start with car in ignore case.
You just need to indicate two more things to use a general value in your binary: that it's a binary, and the size of that binary.
filterCar(Value) when is_binary(Value) ->
MyF = fun(List) ->
Size = byte_size(Value),
case lists:keyfind(name, 1, List) of
{name, <<Value:Size/binary, _Rest/binary>>} ->
true;
_ ->
false
end
end,
MyF.
First we changed filterGuard to take one argument, Value, which is the pattern we want to look for. We use a guard on the function to ensure Value is a binary. Inside the internal fun we first retrieve the size of Value via byte_size/1, which we need so that we can set the expected field size in the matching binary. This leads to the key change, which is <<Value:Size/binary, _Rest/binary>>: we set the expected size of the Value field, and we define it as a binary field.
With this change in place, we can successfully apply it to your List variable, passing <<"red">> for Value:
1> lists:filter(filterCar(<<"red">>), List).
[[{name,<<"redCar1">>},{turbo,true}],
[{name,<<"redCar2">>},{turbo,true}]]

Find the minimum value in a map

I have a map organized as follows.Key is a simple term lets say an integer but the value is complex tuple {BB,CC,DD}. What is the best way to find the minimum CC in the map ? So far I have the following
-module(test).
-author("andre").
%% API
-export([init/0]).
init() ->
TheMap = build(maps:new(), 20),
io:format("Map: ~p~n", [TheMap]),
AKey = hd(maps:keys(TheMap)),
AValue = maps:get(AKey, TheMap),
maps:fold(fun my_min/3, {AKey, AValue}, TheMap).
build(MyMap, Count) when Count == 0 ->
MyMap;
build(MyMap, Count) ->
NewMap = maps:put(Count, {random:uniform(100), random:uniform(100), random:uniform(100)}, MyMap),
build(NewMap, Count - 1).
my_min(Key, {A,B,C}, {MinKey, {AA,BB,CC}}) ->
if B < BB -> {Key, {A,B,C}};
B >= BB -> {MinKey, {AA,BB,CC}}
end.
My map is small so I am not too worried about the usage of AKey and AValue to find initial values for the fold, but I was wondering if there was a better way, or other data structure.
--
Thanks.
What you have is close to a good solution, but it can be improved. There's no need to dig out the first key and value to use an the initial value for the fold, since you can just pass an artificial value instead and make your fold function deal with it. Also, you can improve your use of pattern matching in function heads. Lastly, use start instead of init since that makes it easier to invoke when calling erl from the command line.
Here's an improved version:
-module(test).
-author("andre").
%% API
-export([start/0]).
start() ->
TheMap = build(maps:new(), 20),
io:format("Map: ~p~n", [TheMap]),
maps:fold(fun my_min/3, {undefined, undefined}, TheMap).
build(MyMap, 0) ->
MyMap;
build(MyMap, Count) ->
NewMap = maps:put(Count, {random:uniform(100), random:uniform(100), random:uniform(100)}, MyMap),
build(NewMap, Count - 1).
my_min(Key, Value, {undefined, undefined}) ->
{Key, Value};
my_min(Key, {_,B,_}=Value, {_, {_,BB,_}}) when B < BB ->
{Key, Value};
my_min(_Key, _Value, Acc) ->
Acc.
The my_min/3 fold function has three clauses. The first matches the special start value {undefined, undefined} and returns as the new accumulator value whatever {Key, Value} it was passed. The benefit of this is not only that you avoid special processing before starting the fold, but also that if the map is empty, you'll get the special value {undefined, undefined} as the result and you can handle it accordingly. The second clause uses a guard to check if B of the value is less than the BB value in the fold accumulator, and if it is, return {Key, Value} as the new accumulator value. The final clause just returns the existing accumulator value, since this clause is called only for values greater than or equal to that in the existing accumulator.
You might also look into using a simple list of key/value tuples, since for a small number of elements it might outperform a map. If your measurements indicate you should use a list, a similar fold would work for it as well.
-module(test).
-author("andre").
%% API
-export([init/0]).
init() ->
TheMap = build(maps:new(), 24),
io:format("Map: ~p~n", [TheMap]),
List = maps:to_list(TheMap),
io:format("List: ~p~n", [List]),
Fun = fun({_, {_, V1, _}} = Element, {_, {_, V2, _}}) when V1 < V2 ->
Element;
(_, Res) ->
Res
end,
Res = lists:foldl(Fun, hd(List), tl(List)),
io:format("Res: ~p~n", [Res]).
build(MyMap, Count) when Count == 0 ->
MyMap;
build(MyMap, Count) ->
NewMap = maps:put(Count, {random:uniform(100), random:uniform(100), random:uniform(100)}, MyMap),
build(NewMap, Count - 1).
You can use maps:to_list/1 to convert the map to a list, then you can use lists:foldl/3 to calculate the minimun value.

Sort list of list erlang

PropertyInfo = [
[{LandNo, Acquisition, Heir, Property, LandTypeCount, LandType}],
[{LandNo, Acquisition, Heir, Property, LandTypeCount, LandType}],
[{LandNo, Acquisition, Heir, Property, LandTypeCount, LandType}],
[{LandNo, Acquisition, Heir, Property, LandTypeCount, LandType}],
]
PropertyInfo is a list of lists containing database objects in tuple, where Heir:code() will return a 6-digit code eg. "010011", "00209", ""020011".
How can I sort this list in erlang by using that Heir code?
By using lists:sort/2 and an ordering function:
manual excerpt:
sort(Fun, List1) -> List2
Types:
Fun = fun((A :: T, B :: T) -> boolean())
List1 = List2 = [T]
T => term()
Returns a list containing the sorted elements of List1,
according to the ordering function Fun. Fun(A, B) should return true
if A compares less than or equal to B in the ordering, false
otherwise.
Ordering fun could look like this:
fun([Tuple1],[Tuple2]) ->
Prop1 = element(3,Tuple1);
Prop2 = element(3,Tuple2);
case {Prop1:code(),Prop2:code()} of
{Same,Same} -> true;
{Code1,Code2} -> SomeComparisonFun(Code1,Code2)
end
end
This leaves you to provide a function that can compare those values, once you've decided on a metric that let's you say which value should be greater than some other one.
SortedList = lists:sort(
fun({_, _, A, _, _, _}, {_, _, B, _, _, _}) ->
A:code() =< B:code()
end,
PropertyInfo).
This was very simple, I used this inbuilt lists:sort() function.

Resources