Different views for same erlang record - erlang

Suppose I have a record
-record(expense, {uuid, amount, tags}).
I would like to have a displayable version of the record. Tags field contains tag unique ids. I would like to display the name of tags on a form instead of unique ids. How would you do it in Erlang ? Usually, in a OOP language, you would do a ViewModel to have different displayable version of the same object.
Options 1
Use the same record with different data format but I think it would break the interface contract ; one will not be able to know which version of the record one have.
Option 2
Create another record
-record(expense_view1, {uuid, amount, tags}).
But it would create a lot of duplicated records.
Option 3
Use a tuple or map. Tuple is difficult to maintain if I add more fields to the record, and maps doesn't garantee the safety of field names.

Tags field contains tag unique ids. I would like to display the name
of tags on a form instead of unique ids.
How about this:
-module(a).
-compile(export_all).
-record(expense, {uuid, amount, tags}).
show_action(#expense{uuid=UUID, amount=Amount, tags={A, B, C} }) ->
TagConversions= #{1 => "Joe", 2 => "Tammy", 3 => "Bob"},
A_Conv = maps:get(A, TagConversions, "Nathan"),
B_Conv = maps:get(B, TagConversions, "Nathan"),
C_Conv = maps:get(C, TagConversions, "Nathan"),
io:format("~w, ~w, {~s,~s,~s}~n",
[UUID, Amount, A_Conv, B_Conv, C_Conv]).
go() ->
Expense1 = #expense{uuid=1, amount=10, tags={1,2,3} },
show_action(Expense1).
In the shell:
12> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
13> a:go().
1, 10, {Joe,Tammy,Bob}
ok
I would like to have a displayable version of the record.
But it would create a lot of duplicated records.
Yes, but in OOP don't you have a proliferation of View objects that contain all or a subset of the data in the Model objects?
Options 1 Use the same record with different data format but I think
it would break the interface contract ; one will not be able to know
which version of the record one have.
You could structure the record to leave a blank spot for the tag names corresponding to the tag ids, then fill in the tag names when you are able:
-module(a).
-compile(export_all).
-record(tag, {id, name=""}).
-record(expense, {uuid, amount, tags}).
show_action(Expense = #expense{uuid=UUID, amount=Amount, tags={A, B, C} }) ->
TagConversions= #{1 => "Joe", 2 => "Tammy", 3 => "Bob"},
A_Conv = maps:get(A#tag.id, TagConversions, "Nathan"),
B_Conv = maps:get(B#tag.id, TagConversions, "Nathan"),
C_Conv = maps:get(C#tag.id, TagConversions, "Nathan"),
io:format("~w, ~w, {~s,~s,~s}~n",
[UUID, Amount, A_Conv, B_Conv, C_Conv]),
Expense#expense{tags={
A#tag{name=A_Conv},
B#tag{name=B_Conv},
C#tag{name=C_Conv}
}}.
go() ->
Expense1 = #expense{uuid=1, amount=10,
tags={#tag{id=1},
#tag{id=2},
#tag{id=3} }
},
show_action(Expense1).
In the shell:
5> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
6> a:go().
1, 10, {Joe,Tammy,Bob}
{expense,1,10,{{tag,1,"Joe"},{tag,2,"Tammy"},{tag,3,"Bob"}}}

You could choose the view mode in the access function:
-module (tuple).
-export ([get/1,get/2,start/0,stop/0]).
-record(expense, {uuid, amount, tags}).
%%%%%%%%% Interfaces %%%%%%%%%
% start the server with a static map
start() ->
Pid =spawn(fun() -> loop(#{1 => one, 2 => two, 3 => three}) end),
register(server, Pid).
stop() ->
server ! stop.
% By default for "external users" get the view with value
get(T) ->
get(T,value).
% for "internal usage" it is possible to choose either the id view or the value view
get(T,value) ->
Values = lists:map(fun get_value/1, T#expense.tags),
T#expense{tags = Values};
get(T,id) ->
T.
%%%%%%%%% server %%%%%%%%%%
% the server is in charge to store the id => value association
% it could be also stored in an ETS, a database ...
loop(Ids) ->
receive
stop ->
done;
{From, get_value, Id} ->
% the choice is made to do not crash if the id does not exist
From ! {ok,maps:get(Id, Ids, undefined)},
loop(Ids)
end.
%%%%%%%%% private %%%%%%%%%
get_value(Id) ->
server ! {self(), get_value, Id},
receive
{ok,Value} ->
Value
end.
which gives in the shell:
1> c(tuple).
{ok,tuple}
2> rr(tuple).
[expense]
3> T = #expense{uuid = 12345, amount = 20000, tags = [1,3,4]}.
#expense{uuid = 12345,amount = 20000,tags = [1,3,4]}
4> tuple:start().
true
5> tuple:get(T).
#expense{uuid = 12345,amount = 20000,
tags = [one,three,undefined]}
6> tuple:get(T,value).
#expense{uuid = 12345,amount = 20000,
tags = [one,three,undefined]}
7> tuple:get(T,id).
#expense{uuid = 12345,amount = 20000,tags = [1,3,4]}
8> tuple:stop().
stop

Related

Making a counter of repeating elements in a list and add it to a map ERLANG

i have to make a function to count all the repeating elements on a list and add said elements (with how frequent they are) into a map.
lets say my list is: [rojo, verde, rojo, azul]
my map has to look like: #{rojo => 2, verde => 1, azul => 1}
i have tried for quite some time now and im unable to come up with a solution.
Combining #rorra and #Pascal solutions…
freq([], Map) ->
Map;
freq([H|T], Map) ->
freq(T, maps:update_with(H, fun inc/1, 1, Map)).
inc(X) -> X + 1.
test() ->
List = [rojo, verde, rojo, azul],
#{rojo := 2, verde := 1, azul := 1} =
freq(List, #{}).
An easy way is to use a map, and by doing it, you can then go through the list, and check whether the map has the key being processed or not, if it doesn't have it, you create it with a value of 1, otherwise you update it by adding 1.
freq([], Map) ->
Map;
freq([H|T], Map) ->
NewMap = case maps:find(H, Map) of
{ok, Value} -> Map#{H := Value + 1};
_ -> Map#{H => 1}
end,
freq(T, NewMap).
List = [rojo, verde, rojo, azul],
freq(List, #{}).
Same answer than #rorra, but using the erlang libraries and anonymous functions:
1> List = [rojo, verde, rojo, azul].
[rojo,verde,rojo,azul]
2> Freq = fun(InputList) ->
2> lists:foldl(fun(Item,Acc)->
2> Inc = fun(Count)-> Count+1 end,
2> maps:update_with(Item,Inc,1,Acc)
2> end,
2> #{},InputList)
2> end.
#Fun<erl_eval.44.79398840>
3> Freq(List).
#{azul => 1,rojo => 2,verde => 1}
Just for the fun of fun. rorra answer suits better when starting with Erlang.

String function clause matching

I'm running into a problem when writing some simple erlang code for an old Advent of Code task.
The following program is supposed to read lines, group characters in a string by occurrence and then count the number of lines that have a repeat of three characters.
count_occurrences([], Map) -> Map;
count_occurrences([H | T], Map) ->
count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).
count(Line, Count) ->
Map = count_occurrences(Line, #{}),
case lists:member(3, maps:values(Map)) of
true -> Count + 1;
false -> Count
end.
run() ->
{ok, Binary} = file:read_file("data.txt"),
Lines = binary:split(Binary, <<"\n">>, [global]),
Result = lists:foldl(fun count/2, 0, Lines),
Result.
However, I get this error message:
10> c(day2).
{ok,day2}
11> day2:run().
** exception error: no function clause matching day2:count_occurrences(<<"bpacnmelhhzpygfsjoxtvkwuor">>,#{}) (day2.erl, line 5)
in function day2:count/2 (day2.erl, line 10)
in call from lists:foldl/3 (lists.erl, line 1263)
I don't understand why <<"bpacnmelhhzpygfsjoxtvkwuor">>,#{} doesn't match the second "count_occurrences" function clause - a string is the same as a list, right? Why doesn't it match [H | T]?
Check out this example:
-module(a).
-compile(export_all).
go([_H|_T], _X) ->
"First arg was a list";
go("a", _X) ->
"First arg was a string";
go(<<"a">>, _X) ->
"First arg was a binary".
In the shell:
5> a:go(<<"a">>, #{a=>1, b=>2}).
"First arg was a binary"
and:
6> a:go("a", #{a=>1, b=>2}).
"First arg was a list"
a string is the same as a list, right?
Yes, a double quoted string is a shortcut for creating a list of integers where the integers in the list are the ascii codes of the characters. Hence, the second function clause above will never match:
a.erl:6: Warning: this clause cannot match because a previous clause
at line 4 always matches
But....a binary, such as <<"abc">> is NOT a string, and therefore a binary is not a shortcut for creating a list of integers.
8> "a" =:= [97].
true
Okay, you knew that. But, now:
9> "a" =:= <<"a">>.
false
10> <<"a">> =:= <<97>>.
true
11> "a" =:= <<97>>.
false
And, finally:
13> <<"abc">> =:= <<97, 98, 99>>.
true
The last example shows that specifying a double quoted string inside a binary is just a shortcut for specifying a comma separated list of integers inside a binary--however specifying a double quoted string inside a binary does not somehow convert the binary to a list.
Note that you can also iterate through a binary with only slightly different syntax:
count_occurrences(<<>>, Map) -> Map;
count_occurrences(<<H, T/binary>>, Map) ->
count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).
By default, H is assumed to be a byte, but you can add modifiers to specify how many bits you want to select, and more. See the documentation for the Bit Syntax.
You get this error cuz function count_occurrences/2 expect first argument list - [<<"bpacnmelhhzpygfsjoxtvkwuor">>] or "bpacnmelhhzpygfsjoxtvkwuor" but was put binary - <<"bpacnmelhhzpygfsjoxtvkwuor">>. Double check input data Line in function count/2 of module day2.erl at line 10:
1> is_list([]).
true
2> is_list("").
true
3> is_list(<<"">>).
false
4> is_list(binary_to_list(<<"">>)).
true

Erlang integers in list and use them in formula's

I am learning Erlang and how to work with lists but I have a problem. I am making a program with the use of solving math problems for example Tot = X*Y*Z.
First I give in a variable to set the amount of problems I want to solve like this:
inTestes(Tests) ->AmountOfTests = Tests.
Now to hold my different variables I also made a record: -record(specs,{x,y,z}). that holds a tuplet with the 3 variables.
I try to fill this tuplet: inSpecs(X,Y,Z)-> #specs =[x=X,y=Y,z=Z]. But this doesn't work.
I think I need to use something like lists:append(The variables here)->#specs{x=X,y=Y,z=Z} but I just don't seem to get it right.
When I just do inSpecs(X,Y,Z)-> Specs =[X,Y,Z]. I get 1 list for example [10,20,30]
How can I hold multiple lists like this according to the AmountOfTests?
Could someone give me some guidance?
I try to fill this tuplet:
inSpecs(X,Y,Z)-> #specs =[x=X,y=Y,z=Z].
But this doesn't work
And why do you think that should work? Do you think the following should work to create a list:
MyList = ! 3, 4; 5].
Computer programming requires exact syntax--not 5 out of 7 characters that are correct.
Please point to any book, website, or documentation that says that you can create a record with this syntax:
#specs =[x=X,y=Y,z=Z].
Here's what you can do:
-module(my).
-compile(export_all).
-record(specs, {x,y,z}).
inSpecs(X,Y,Z)-> #specs{x=X,y=Y,z=Z}.
In the shell:
1> c(my).
my.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,my}
2> Record = my:inSpecs(10, 20, 30).
{specs,10,20,30}
3> rr("my.erl").
[specs]
4> Record.
#specs{x = 10,y = 20,z = 30}
5> Record#specs.x.
10
When I just do inSpecs(X,Y,Z)-> Specs =[X,Y,Z]. I get 1 list for
example [10,20,30]
How can I hold multiple lists like this according to the
AmountOfTests?
Suppose AmountOfTests is equal to 3, what should inSpecs/3 return?
Response to comment:
Here's how you can create three specs with the same data:
-module(my).
-compile(export_all).
-record(specs, {x,y,z}).
inSpecs(X,Y,Z)-> #specs{x=X,y=Y,z=Z}.
create_specs(0) -> [];
create_specs(N) -> [inSpecs(1,2,3) | create_specs(N-1)].
In the shell:
1> c(my).
my.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,my}
2> rr(my).
[specs]
3> Specs = my:create_specs(3).
[#specs{x = 1,y = 2,z = 3},
#specs{x = 1,y = 2,z = 3},
#specs{x = 1,y = 2,z = 3}]
Or, if you have the data for the specs in a list like this:
SpecData = [ [1,2,3], [12,13,14], [7,8,9] ].
Then you can create a function like this:
-module(my).
-compile(export_all).
-record(specs, {x,y,z}).
inSpecs(X,Y,Z)-> #specs{x=X,y=Y,z=Z}.
create_specs([]) -> [];
create_specs([ [X,Y,Z] | Tail]) ->
[inSpecs(X,Y,Z) | create_specs(Tail)].
In the shell:
17> f().
ok
18> c(my).
my.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,my}
19> SpecData = [ [1,2,3], [12,13,14], [7,8,9] ].
[[1,2,3],[12,13,14],[7,8,9]]
20> Specs = my:create_specs(SpecData).
[#specs{x = 1,y = 2,z = 3},
#specs{x = 12,y = 13,z = 14},
#specs{x = 7,y = 8,z = 9}]
It seems like you're trying to get the 3 values from your list and put them in your record. You can do that with pattern matching. If you know the list you are being sent has exactly 3 values in it, you can do it with the following:
inTests( [X, Y, Z] ) ->
#specs{ x = X, y = Y, z = Z}.

Fields with common names in different records

I have some records with similar fields, like this:
-define(COMMON_FIELDS, common1, common2, common3).
-record(item1, a, b, c, ?COMMON_FIELDS).
-record(item2, x, y, z, ?COMMON_FIELDS).
But later I need to write similar code for every record:
Record#item1.common1,
Record#item1.common2,
Record#item1.common3
and:
Record#item2.common1,
Record#item2.common2,
Record#item2.common3
Is there way to write one function for access to same fields in different records?
Is there way to write one function for access to same fields in
different records?
1) Pattern matching in multiple function clauses:
-module(x1).
-export([read/1]).
-define(COMMON_FIELDS, common1, common2, common3).
-record(item1, {x, ?COMMON_FIELDS}). %Note that you defined your records incorrectly.
-record(item2, {y, ?COMMON_FIELDS}).
read(#item1{common1=C1, common2=C2, common3=C3} = _Item) ->
io:format("~p, ~p, ~p~n", [C1, C2, C3]);
read(#item2{common1=C1, common2=C2, common3=C3} = _Item) ->
io:format("~p, ~p, ~p~n", [C1, C2, C3]).
...
25> c(x1).
{ok,x1}
26> rr(x1).
[item1,item2]
27> A = #item1{x=10, common1="hello", common2="world", common3="goodbye"}.
#item1{x = 10,common1 = "hello",common2 = "world",
common3 = "goodbye"}
28> B = #item2{y=20, common1="goodbye", common2="mars", common3="hello"}.
#item2{y = 20,common1 = "goodbye",common2 = "mars",
common3 = "hello"}
29> x1:read(A).
"hello", "world", "goodbye"
ok
30> x1:read(B).
"goodbye", "mars", "hello"
ok
Note the export statement--it's a list of length 1, i.e. the module exports one function. The output shows that the read() function can read records of either type.
2) A case statement:
If for some reason, by stating one function you mean one function clause, you can do this:
read(Item) ->
case Item of
#item1{common1=C1, common2=C2, common3=C3} -> true;
#item2{common1=C1, common2=C2, common3=C3} -> true
end,
io:format("~p, ~p, ~p~n", [C1, C2, C3]).
You can use exprecs parse transform from parse_transe.
-module(parse).
-compile({parse_transform, exprecs}).
-record(item1, {x, common1, common2}).
-record(item2, {y, common1, common2}).
-export_records([item1, item2]).
-export([p/0]).
f() ->
R1 = #item1{x=1, common1=foo1, common2=bar1},
R2 = #item2{y=2, common1=foo2, common2=bar2},
['#get-'(Field, Rec) || Field <- [common1, common2], Rec <- [R1, R2]].
...
1> c(parse).
{ok,parse}
2> parse:f().
[foo1,foo2,bar1,bar2]
It might make sense to factor out the common fields into a single field in each record companies containing a record with all the common data or even a tulple. Then refactor your code to do all common processing to its own function.
You still need to pattern match every top level record to get the common sub record. But somewhere you probably want to do the processing specific to each record kind and there you can already match out the common field.
-record(common, {c1, c2, c3}).
-record(item1, {a, b, c, com}).
...
process_item(#item1{a=A, b=B, c=C, com=Com}) ->
process_abc(A, B, C),
process_common(Com),
...;
process_item(#item2{x=X, y=Y ...
Data structures like this might also be a indication to use the new Map data type instead of records.

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}]]

Resources