Querying an erlang ETS table that uses a tuple key - erlang

I have an ETS table where the key consist of a record like {shell,TIME,NAME,ID} and I want to allow a user to search for entries using a combination of any of those.
IE: where
TIME < LOW_VALUE and TIME > HIGH_VALUE
or
TIME < 40 and NAME == "SAM"
or
ID == 123
I understand how to use fun2ms, but not well enough to know whether there's some clean one-liner way of doing this. My current solution is matching a search request to the 6 possible combinations of search types, and it just feels dirty compared to all my other erlang code that makes extensive use of pattern matching.
Can you guys help me with making use of fun2ms or ETS tables in a more intelligent way? I'm very sure this query can be done with one line. Here is an example of one of the 6 functions I use to show you what I have:
getShells_by_Time(Tstart, Tend) ->
Results = ets:select(schedule_row1,ets:fun2ms(
fun(A = #activity{shell = ActivityShell,activity_data = S1Data})
when (ActivityShell#activity_shell.tsched < Tend)
andalso (ActivityShell#activity_shell.tsched > Tstart) ->
ActivityShell
end)),
EDIT:
So This is what I'm trying to do so far:
I have a record that I want to default to:
-record(s1shell_query,{tsched = {_ScLow,_ScHigh}, id = _ID, type = _Type}).
which means the user can change any of the record terms for the things they want to match. The problem is that you can't have unbound variables defaulted in a record.
My match spec function looks like this:
{ScLow,ScHigh} = ShellQuery#s3shell_query.tsched,
ets:select(Table, ets:fun2ms(
fun(#stage3Activity{shell = #activity_shell{tsched = Tsched, id = ID, type = Type}})
when Tsched < ScLow, Tsched>ScHigh,ID == ID, Type == Type ->
#activity_shell{tsched = Tsched,id = ID,type = Type}
end)).
So I'm stuck trying to figure out how I can ignore matching things the user didn't put into the shell query record.

You can use custom match specs guards for searching instead of the ets:fun2ms/1, for example:
-module(match_spec_guards).
-export([new/0, populate/0, search/1]).
-record(row, {
key :: {shell, Time :: integer(), Name :: string(), ID :: term()},
value :: term()
}).
new() ->
ets:new(?MODULE, [set, named_table, {keypos, #row.key}]).
populate() ->
[ets:insert(?MODULE, #row{key = {shell, I, integer_to_list(I), I * 1000}, value = I * 1000})
|| I <- lists:seq(1,1000)].
search(Filters) ->
Guards = [filter_to_guard(Filter) || Filter <- Filters],
MatchHead = {row, {shell, '$1', '$2', '$3'}, '_'},
Result = ['$_'],
ets:select(?MODULE, [{MatchHead, Guards, Result}]).
filter_to_guard({time_higher_than, X}) -> {'<', X, '$1'};
filter_to_guard({time_lower_than, X}) -> {'>', X, '$1'};
filter_to_guard({name_is, X}) -> {'==', '$2', X};
filter_to_guard({id_is, X}) -> {'==', '$3', X}.
You can use it like:
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]
Eshell V10.7.1 (abort with ^G)
1> c(match_spec_guards).
{ok,match_spec_guards}
2> match_spec_guards:new().
match_spec_guards
3> match_spec_guards:populate().
[true,true,true,true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,true,true,true,true,
true,true,true,true,true,true|...]
4> match_spec_guards:search([{time_higher_than, 10}, {time_lower_than, 20}]).
[{row,{shell,15,"15",15000},15000},
{row,{shell,16,"16",16000},16000},
{row,{shell,17,"17",17000},17000},
{row,{shell,12,"12",12000},12000},
{row,{shell,13,"13",13000},13000},
{row,{shell,11,"11",11000},11000},
{row,{shell,19,"19",19000},19000},
{row,{shell,14,"14",14000},14000},
{row,{shell,18,"18",18000},18000}]
5> match_spec_guards:search([{id_is, 15000}]).
[{row,{shell,15,"15",15000},15000}]
6> match_spec_guards:search([{name_is, "15000"}]).
[]
7> match_spec_guards:search([{name_is, "15"}]).
[{row,{shell,15,"15",15000},15000}]
You have more information about the match specs syntaxis in ets:select/2
I'd recommend using
MatchHead = #row{key = {shell, '$1', '$2', '$3'}, value = '_'},
even if you need to tweak the record definition to allow the '$x' and '_' atoms.
Also, please consider compiling the match spec and/or using continuations if you are using big tables.
EDIT
By abusing the complete order between erlang terms and the useful '_' defaults in the search record, you're able to obtain what you need:
-module(match_spec_guards).
-export([new/0, populate/0, search/1]).
-record(row, {
key :: {shell, Time :: integer(), Name :: string(), ID :: term()},
value :: term()
}).
-record(s1shell_query, {
tsched_low = 0,
tsched_high = undefined,
id = '_',
name = '_'
}).
new() ->
ets:new(?MODULE, [set, named_table, {keypos, #row.key}]).
populate() ->
[ets:insert(?MODULE, #row{key = {shell, I, integer_to_list(I), I * 1000}, value = I * 1000})
|| I <- lists:seq(1,1000)].
search(#s1shell_query{tsched_low = Low, tsched_high = High} = Query) ->
MatchHead = #row{key = {shell, '$1', Query#s1shell_query.name, Query#s1shell_query.id}, value = '_'},
Guards = [{'>', High, '$1'}, {'=<', Low, '$1'}],
ets:select(?MODULE, [{MatchHead, Guards, ['$_']}]).
The id and name will be taken from the record if it's defined (and use '_' if it's not), and the guards will perform the filtering naturally if they are defined, and using the complete order for the higher limit if they are not (atoms are always higher than numbers, regardless of the atom and the number).
An example of the usage follows:
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]
Eshell V10.7.1 (abort with ^G)
1> c(match_spec_guards).
{ok,match_spec_guards}
2> match_spec_guards:new().
match_spec_guards
3> match_spec_guards:populate().
[true,true,true,true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,true,true,true,true,
true,true,true,true,true,true|...]
4> rr(match_spec_guards).
[row,s1shell_query]
7> match_spec_guards:search(#s1shell_query{id = 14000}).
[#row{key = {shell,14,"14",14000},value = 14000}]
8> match_spec_guards:search(#s1shell_query{name = "14"}).
[#row{key = {shell,14,"14",14000},value = 14000}]
9> match_spec_guards:search(#s1shell_query{tsched_high = 20}).
[#row{key = {shell,1,"1",1000},value = 1000},
#row{key = {shell,15,"15",15000},value = 15000},
#row{key = {shell,6,"6",6000},value = 6000},
#row{key = {shell,16,"16",16000},value = 16000},
#row{key = {shell,8,"8",8000},value = 8000},
#row{key = {shell,2,"2",2000},value = 2000},
#row{key = {shell,9,"9",9000},value = 9000},
#row{key = {shell,17,"17",17000},value = 17000},
#row{key = {shell,12,"12",12000},value = 12000},
#row{key = {shell,7,"7",7000},value = 7000},
#row{key = {shell,13,"13",13000},value = 13000},
#row{key = {shell,10,"10",10000},value = 10000},
#row{key = {shell,3,"3",3000},value = 3000},
#row{key = {shell,11,"11",11000},value = 11000},
#row{key = {shell,19,"19",19000},value = 19000},
#row{key = {shell,14,"14",14000},value = 14000},
#row{key = {shell,5,"5",5000},value = 5000},
#row{key = {shell,4,"4",4000},value = 4000},
#row{key = {shell,18,"18",18000},value = 18000}]
10> match_spec_guards:search(#s1shell_query{tsched_low = 998}).
[#row{key = {shell,998,"998",998000},value = 998000},
#row{key = {shell,999,"999",999000},value = 999000},
#row{key = {shell,1000,"1000",1000000},value = 1000000}]

Related

Erlang Mnesia Select result not same as read, foldl

I have a situation with 3 use cases for returning data from an mnesia table
1. return all values of a table so I use a foldl,
2. return 1 row so I use read
3. return a variable number of records based on criteria so I use select.
I would like to use the same code to manage the results, but the select returns a different data structure. I am hoping someone can help me restructure my select to return the same as the others.
below is sample code of the issue and the results. The issue is the select does not return the record name for the table as does read and foldl.
-module(testselect2).
-export([runtest/0]).
-record(record_a, {b, c, d}).
-record(record_b, {record_a, e}).
-record(record_c, {record_b, f, intval}).
runtest() ->
mnesia:create_schema([node()]),
mnesia:start(),
mnesia:create_table(record_c, [{attributes, record_info(fields, record_c)}]),
A1 = #record_a{b = "R1", c = "T1", d = "C1"},
B1 = #record_b{record_a = A1, e = "E1"},
C1 = #record_c{record_b = B1, f = "F1", intval = 100},
A2 = #record_a{b = "R2", c = "T2", d = "C2"},
B2 = #record_b{record_a = A2, e = "E2"},
C2 = #record_c{record_b = B2, f = "F2", intval = 200},
A3 = #record_a{b = "R3", c = "T3", d = "C3"},
B3 = #record_b{record_a = A3, e = "E3"},
C3 = #record_c{record_b = B3, f = "F3", intval = 300},
{atomic, Rw} = mnesia:transaction(
fun () ->
mnesia:write(C1),
mnesia:write(C2),
mnesia:write(C3)
end),
io:fwrite("Result write = ~w~n", [Rw]),
{atomic, Rr} = mnesia:transaction(
fun () ->
mnesia:read({record_c, B1})
end),
io:fwrite("Result read = ~w~n", [Rr]),
{atomic, Rf} =
mnesia:transaction(fun () ->
mnesia:foldl(fun (Rec, Acc) -> [Rec | Acc] end,
[],
record_c)
end),
io:fwrite("Result foldl = ~w~n", [Rf]),
MatchHead = #record_c{record_b='$1', f='$2', intval='$3'},
Guard = {'>', '$3', 100},
Result = {{'$1', '$2', '$3'}},
{atomic, Rs} = mnesia:transaction(
fun () ->
mnesia:select(record_c, [{MatchHead, [Guard], [Result]}])
end),
io:fwrite("Result select = ~w~n", [Rs]).
=====
RESULTS
44> testselect2:runtest().
Result write = ok
Result read = [{record_c,{record_b,{record_a,[82,49],[84,49],[67,49]},[69,49]},[70,49],100}]
Result foldl = [{record_c,{record_b,{record_a,[82,49],[84,49],[67,49]},[69,49]},[70,49],100},{record_c,{record_b,{record_a,[82,51],[84,51],[67,51]},[69,51]},[70,51],300},{record_c,{record_b,{record_a,[82,50],[84,50],[67,50]},[69,50]},[70,50],200}]
Result select = [{{record_b,{record_a,[82,51],[84,51],[67,51]},[69,51]},[70,51],300},{{record_b,{record_a,[82,50],[84,50],[67,50]},[69,50]},[70,50],200}]
ok
As you can see above read and foldl records start with {record_c,{... where select is missing the record_c and just has {{...
I have been unable to find a way to get select to return the same structure so my processing code can work for all 3 use cases. Any suggestions would be greatly appreciated.
I'm no mnesia expert, but I know when you use an ETS match expression, you determine what the result looks like. You use Result = {{'$1', '$2', '$3'}} to create your result terms, which makes them come out as three-tuples in a one-tuple, as we see in your output. Per ets:select/1, you want to use the special variable '$_' to return the whole matched object, so this should work in place of your Result = ... line:
Result = '$_',

Adding to an existing value in Erlang

I am attempting to create a function that stores a number into a record and then adds value X to that number every time the function runs.
Value: 5
Run Function (Add One): 1
Value should be: 6
Run Function (Add One): 1
value should be 7
I tried to use a record:
-record(adder,{value :: integer()}).
---function
Number = random:uniform(6),
L=#added{value = Number + #added.value}.
This does not work as it resets the value every time. Any suggestions?
Take a look at this code:
-module(test).
-export([add/1]).
-record(adder, {value=6}).
add(X) ->
#adder{value = X + #adder.value}.
If you compile this in your shell, any call to "add(3)" will result in "{adder,5}" and not in "{adder, 9}". Take a look:
Eshell V6.4 (abort with ^G)
1> c(test).
{ok,test}
2> test:add(3).
{adder,5}
3> test:add(3).
{adder,5}
How come? This is because records are actually tuples. The expression "#adder.value" in the last line is evaluated as the position of the field "value" in your adder tuple, which is 2. Let's have some proof. Change the definition of your record:
-module(test).
-export([add/1]).
-record(adder, {field1, field2, value=6}).
add(X) ->
#adder{value = X + #adder.value}.
Now, recompiling your code and calling add(3) again would result in
1> c(test).
{ok,test}
2> test:add(3).
{adder,undefined,undefined,7}
I've asked myself, how you came up with this question. Didn't you want your function to be something like this?
add2(#adder{value = V} = R, X) ->
R#adder{value = V + X}.
-record(adder, {value = 5}).
add(Value) ->
add(#adder{}, Value).
add(#adder{value =V} = Adder, Value) ->
Adder#adder{value = V + Value}.
test() ->
R1 = add(1),
io:format("~p~n", [R1]),
R2 = add(R1, 10),
io:format("~p~n", [R2]).
Here is the output of running test:test().
6> c(test).
{ok,test}
7> test:test().
{adder,6}
{adder,16}
ok

converting string list to ternary tree in sml

I would like to convert a string list into ternary tree.The tree should have the following datatype
datatype ttree = node of string*ttree list*string|leaf of string*string*string
for example,
["<root>","<a3>","<b2>","Father","</b2>","<b3>","Mother","</b3>","</a3>","<a1>","<ffx>","AAA",...] : string list
should be converted to
val test =
node
("<root>",
[node
("<a3>",
[leaf ("<b2>","Father","<b2>"),leaf ("<b3>","Mother","<b3>")],
"<a3>"),
node
("<a1>",[leaf ("<ffx>","AAA","<ffx>"),leaf ("<ff>","BBB","<ff>")],
"<a1>")],"<root>") : ttree
Not the most efficient solution, but this should solve the problem:
datatype ttree = node of string*ttree list*string | leaf of string*string*string
val l = ["<root>",
"<a3>",
"<b2>","Father","</b2>",
"<b3>","Mother","</b3>",
"</a3>",
"<a1>",
"<ffx>","AAA","</ffx>",
"</a1>"]
(*listToTree(l) = string list to ternary tree*)
fun listToTree(l : string list) : ttree =
let
(*isLeaf(s1, s3) = true if s1=<a> and s3=</a>,
a can be replaced with anything
false otherwise*)
fun isLeaf(s1 : string, s3 : string) : bool =
let
val (e1, e3) = (explode s1, explode s3)
val (t1c1::t1) = e1
val t1e = List.last t1
val (t3c1::t3c2::t3) = e3
val t3e = List.last t3
in
if t1c1 = #"<" andalso
t1c1 = t3c1 andalso
t1e = #">" andalso
t1e = t3e andalso
t3c2 = #"/" andalso
t1 = t3 then true else false
end
(*parseUntil(l, until, acc) = (p, r),
where p = acc + a part of l until an element x
that satisfies isLeaf(until,x)
and r = elements of list l after x *)
fun parseUntil(l : string list, until : string, acc : string list)
: string list * string list =
case l of
[] => (rev acc, [])
| x::xs => if isLeaf(until, x)
then (rev acc, xs)
else parseUntil(xs, until, x::acc)
(*parseList(l) = the ttree list of the given string list*)
fun parseList(l : string list) : ttree list =
case l of
[] => []
| x::xs => let
val (parsed, rest) = parseUntil(xs, x, [])
in
if length parsed = 1
then leaf(x, hd xs, x)::parseList(rest)
else node(x, parseList(parsed), x)::parseList(rest)
end
in
hd (parseList l)
end
val result = listToTree l;
It is basically doing this:
Once you get a tag, get the list elements until the close tag.
Call the same parser function with the elements until the close tag and put the result in the list in the node.
Call the same parser function with the elements after the close tag, and append them to the upper level list.
I hope I could help.

FParsec - parser sequences

Say I have some text:
a = "foobarbaz"
b = "foobar"
c = "foo"
d = "rubbish"
e = "foobazbar"
and three parsers foo, bar, and baz for the strings 'foo', 'bar' and 'baz' respectively.
How would I create a parser that would give me the results:
a = ["foo", "bar", "baz"]
b = ["foo", "bar"]
c = ["foo"]
d = []
e = ["foo"]
when run against the inputs above? Basically attempt each possibility until failure whilst constructing a list. I could use user state but I would like to avoid if possible. (I would like to keep the individual parsers themselves ignorant of user state)
the closest I have gotten is something like fooseq below:
let foo = pstring "foo"
let bar = pstring "bar"
let baz = pstring "baz"
let foobar = pipe2 foo bar Seq.of2
let foobarbaz = pipe3 foo bar baz Seq.of3
let fooseq = choice (Seq.map attempt [foobarbaz; foobar; foo |>> Seq.of1 ;])
//(the Seq.ofx functions just take arguments and create a sequence of them)
It seems to me there must be a better way of doing this?
FParsec has no built-in sequence combinator that does exactly what you're looking for, but you could implement one yourself like in the following example:
let mySeq (parsers: seq<Parser<'t,'u>>) : Parser<'t[],'u> =
let ps = Array.ofSeq parsers
if ps.Length = 0 then preturn [||]
else
fun stream ->
let mutable stateTag = stream.StateTag
let mutable reply = ps.[0] stream
let mutable error = reply.Error
let mutable myReply = Reply()
if reply.Status <> Ok then myReply.Result <- [||]
else
// create array to hold results
let mutable xs = Array.zeroCreate ps.Length
xs.[0] <- reply.Result
let mutable i = 1
while i < ps.Length do
stateTag <- stream.StateTag
reply <- ps.[i] stream
error <- if stateTag <> stream.StateTag then reply.Error
else mergeErrors error reply.Error
if reply.Status = Ok then
xs.[i] <- reply.Result
i <- i + 1
else // truncate array and break loop
xs <- Array.sub xs 0 i
i <- ps.Length
myReply.Result <- xs
myReply.Status <- if reply.Status = Error && stateTag = stream.StateTag
then Ok
else reply.Status
myReply.Error <- error
myReply
With the mySeq combinator, you can express your fooSeq parser as
let fooSeq = mySeq [foo; bar; baz]

searching in a list for a record with specific fields and ignoring the rest

I have a record defined as:
1> rd(fact, {a,b,c}).
fact
I create three records and put them in a list
2> F1 = #fact{a=1,b=1,c=1}.
#fact{a = 1,b = 1,c = 1}
(3> F2 = #fact{a=2,b=2,c=2}.
#fact{a = 2,b = 2,c = 2}
3> F3 = #fact{a=3,b=3,c=3}.
#fact{a = 3,b = 3,c = 3}
4> L = [F1,F2,F3].
[#fact{a = 1,b = 1,c = 1},
#fact{a = 2,b = 2,c = 2},
#fact{a = 3,b = 3,c = 3}]
Now, I want to check if the list contains a record in which 'a' is 1 and I don't care for the rest of the fields
(dilbert#Martin-PC)21> lists:member(#fact{a=1}, L).
false
(dilbert#Martin-PC)23> lists:member(#fact{a=1,b=1,c=1}, L).
true
How can I accomplish it?
Or you could use keyfind.
lists:keyfind(1, #fact.a, L).
Records are pure syntactic sugar. When you don't specify the values of the other fields in a record declaration the atom 'undefined' is used. Therefore your search is for:
#fact{a=1, b='undefined', c='undefined'}
... which of course doesn't exist.
Try this instead:
lists:any(fun(#fact{a=A}) -> A =:= 1 end, L).
Or list comprehension:
OneList = [E || E <- L, E#fact.a =:= 1]

Resources