Fetching and updating data in mnesia - erlang

I have multiple mnesia tuples like (GroupID is the primary key)
{GroupID, GroupName, GroupType, GroupDescription, GroupTag, CreatorID, AdminID, MemberList, Counter}.
MemberList = "memberone#xyz,membertwo#xyz,memberthree#xyz".
GroupName = "Any String Value". % e.g.: "basketball"
GroupTag = "comma separated values". % e.g.: "Sports,Cricket,Fifa,Ronaldo"
I will pass a character or word to a function. This function will search the character in GroupName and GroupTag.
If successful then it will return comma separated tuples of GroupID, GroupName, GroupDescription; And Counter should be incremented for the corresponding row.
Suppose in my mnesia database tuples are
{"A", "Cricket", "0", "A group for cricket fans", "Sports, Cricket, Sachin tendulkar", "Xyz", "XYZ", "XYZ", 1},
{"B", "Sports", "0", "A group for Sport fans", "Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar", "Xyz", "XYZ", "XYZ", 0}.
So if I search for "sac", it should give the output
[{"A", "Cricket", "A group for cricket fans"},
{"B", "Sports", "A group for Sport fans"}]
Counter value for group A should be 2 (it was 1, check the last element of the tuple) and for group B should be 1 (it was 0, check the last element of the tuple).
Any pointers?

As far as I know, you cannot build a guard with a call to get the substring from a string, so instead of using a Erlang Match Specification you would have to iterate over all the records and filter the ones that you need.
-module(tuples).
-compile(export_all).
-record(group, {group_id,
group_name,
group_type,
group_description,
group_tag,
creator_id,
admin_id,
member_list,
counter}).
start() ->
mnesia:create_schema([node()]),
mnesia:start().
stop() ->
mnesia:stop(),
mnesia:delete_schema([node()]).
load_data() ->
mnesia:create_table(group,
[{attributes, record_info(fields, group)}]),
Record1 = #group{group_id = "A",
group_name = "Cricket",
group_type = "0",
group_description = "A group for cricket fans",
group_tag = "Spots,Cricket,Sachin tendulkar",
creator_id = "Xyz",
admin_id = "XYZ",
member_list = "XYZ",
counter = 1},
Record2 = #group{group_id = "B",
group_name = "Sports",
group_type = "0",
group_description = "A group for Sport fans",
group_tag = "Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar",
creator_id = "Xyz",
admin_id = "XYZ",
member_list = "XYZ",
counter = 0},
Insert = fun() -> lists:map(fun(X) -> mnesia:write(X) end, [Record1, Record2]) end,
mnesia:transaction(Insert).
show_data() ->
CatchAll = [{'_', [], ['$_']}],
mnesia:dirty_select(group, CatchAll).
query(Substring) ->
Update = fun(Record) ->
NewRecord = Record#group{counter = Record#group.counter + 1},
mnesia:write(NewRecord),
NewRecord
end,
RequiredFields = fun(Record) -> {Record#group.group_id, Record#group.group_name, Record#group.group_description} end,
Constraint =
fun(Group, Acc) ->
case string:str(string:to_lower(Group#group.group_name),
string:to_lower(Substring)) of
0 ->
case string:str(string:to_lower(Group#group.group_tag),
string:to_lower(Substring)) of
0 ->
Acc;
_ ->
NewRecord = Update(Group),
[RequiredFields(Group) | NewRecord]
end;
_->
NewRecord = Update(Group),
[RequiredFields(Group) | NewRecord]
end
end,
Find = fun() -> mnesia:foldl(Constraint, [], group) end,
{_, Data} = mnesia:transaction(Find),
Data.
and to try the code:
Eshell V6.4 (abort with ^G)
1> c("tuples.erl").
{ok,tuples}
2> tuples:start().
ok
3> tuples:load_data().
{atomic,[ok,ok]}
4> tuples:show_data().
[{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",1},
{group,"B","Sports","0","A group for Sport fans",
"Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar","Xyz","XYZ",
"XYZ",0}]
5> tuples:query("sac").
[{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",1}|
{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",2}]
6> tuples:stop().
=INFO REPORT==== 14-Jun-2015::22:14:42 ===
application: mnesia
exited: stopped
type: temporary
ok
7> q().
ok
8>

Related

erlang ets select bad arg

erlang version 18.3
Got an strange error with Erlang ets:select/1
the following code will do select element from table and take them .
if I do
save(10), %% insert 10 data
remove(3) %% remove 3 data per time
it works
if I do
save(6007), %% insert more datas
remove(400) %% remove 400 data per time
it was bad arg in ets:select(Cont) also, it was not the in the first or second loop, but was always there.
any suggestion?
-record(item, {name, age}).
%% start the table
start() ->
ets:new(example_table, [public, {keypos, 2},
named_table,
{read_concurrency, true},
{write_concurrency, true}]).
%% insert n demo data
save(Limit) ->
All = lists:seq(1 ,Limit),
All_rec = [#item{name = {<<"demo">>, integer_to_binary(V)} , age = V} || V <- All],
ets:insert(example_table, All_rec).
%% remove all data, n data per select
remove(Limit) ->
M_head = #item{name = '$1', _ = '_'},
M_guards = [],
M_result = ['$1'],
M_spec = [{M_head, M_guards, M_result}],
case ets:select(example_table, M_spec, Limit) of
'$end_of_table' ->
0;
{Keys, Cont} ->
remove(example_table, Keys, Cont, 0, [])
end.
remove(Table, [], Cont, Count, _Acc) ->
case ets:select(Cont) of
'$end_of_table' ->
Count;
{Keys, Cont_1} ->
remove(Table, Keys, Cont_1, Count, [])
end;
remove(Table,[Key | T], Cont, Count, Acc) ->
case ets:take(example_table, Key) of
[] ->
remove(Table, T, Cont, Count, Acc);
[Rec] ->
io:format("Rec [~p] ~n", [Rec]),
remove(Table, T, Cont, Count + 1, [Rec | Acc])
end.
stack trace
4> example_remove:save(6007).
true
5> example_remove:remove(500).
** exception error: bad argument
in function ets:select/1
called as ets:select({example_table,304,500,<<>>,
[{<<"demo">>,<<"2826">>},
{<<"demo">>,<<"3837">>},
{<<"demo">>,<<"5120">>},
{<<"demo">>,<<"878">>},
{<<"demo">>,<<"1195">>},
{<<"demo">>,<<"1256">>},
{<<"demo">>,<<"1449">>},
{<<"demo">>,<<"5621">>},
{<<"demo">>,<<"5768">>}],
9})
in call from example_remove:remove/5 (d:/workspace/simple-cache/src/example_remove.erl, line 47)
I believe this happens because you simultaneously iterate over the table and modify it.
I suggest wrapping main remove cycle with guards of safe_fixtable
remove(Limit) ->
ets:safe_fixtable(example_table, true),
M_head = #item{name = '$1', _ = '_'},
M_guards = [],
M_result = ['$1'],
M_spec = [{M_head, M_guards, M_result}],
R = case ets:select(example_table, M_spec, Limit) of
'$end_of_table' ->
0;
{Keys, Cont} ->
remove(example_table, Keys, Cont, 0, [])
end,
ets:safe_fixtable(example_table, false),
R.

F# records and mapping

I am new to F# and have been messing around with records and changing them. I am trying to apply my own function with out using map to my list. This is what i have so far. I am just wondering if my approach for how to write a mapping without using the map function the correct way of thinking about it.
module RecordTypes =
// creation of simple record
// immutable by default - key word mutable allows that to change
type Student =
{
Name : string
mutable age : int
mutable major : string
}
// setting up a few records with student information
// studentFive.age <- studentFive.age + 2 ; example of how to change mutable variable
let studentOne = { Name = "bob" ; age = 20 ; major = "spanish" }
let studentTwo= { Name = "sally" ; age = 18 ; major = "english" }
let studentThree = { Name = "frank" ; age = 22 ; major = "history" }
let studentFour = { Name = "lisa" ; age = 19 ; major = "math" }
let studentFive = { Name = "john" ; age = 17 ; major = "philosophy" }
// placing the records into a lits
let studentList = [studentOne; studentTwo; studentThree ;studentFour; studentFive]
// placing the records into a lits
let studentList = [studentOne; studentTwo; studentThree ;studentFour; studentFive]
// itterate through a list and printing each records
printf "the unsorted list of students: \n"
studentList |> List.iter (fun s-> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
// a sort of the records based on the name, can be sorted by other aspects in the records
let sortStudents alist =
alist
|> List.sortBy (function student -> student.age)
let rec selectionSort = function
| [] -> [] //if the list is empty it will return an empty list
| l -> let min = List.min l in // otherwise set a min variable and use the min function to find the smallest item in a list
let rest = List.filter (fun i -> i <> min) l in // set a variable to hold the rest of the list using filter
// Returns a new collection containing only the elements of the collection for which the given predicate returns true
// fun sets up a lambda expression that if ( i -> i <> (not equal boolean) min) if i(the record is not the min put it into a list)
let sortedList = selectionSort rest in // sort the rest of the list that isnt the min
min :: sortedList // :: is an operator that creates a list, left elem appended to right side
let unsortedList = studentList
let sortedList = selectionSort unsortedList
printfn "sorted list based on first name:\n"
sortedList |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
here is where i tried to create my own map with function foo
let foo x = x + 1
let applyOnEachElement (list : Student list) (someFunction) =
list |> List.iter(fun s -> someFunction s.age)
//let agedStudents = applyOnEachElement studentList foo
printf " the students before function is applied to each: \n"
sortedList |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
printf " the student after function is applied to each: \n"
agedStudents |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
In the last comment, the OP mentions his almost complete solution. With a bit of added formatting and a forgotten match construct, it looks as follows:
let rec applyOnEachElement2 (list: Student list) (f) =
match list with
| [] -> []
| hd :: tl -> hd::applyOnEachElement2 f tl
This is quite close to the correct implementation of map function! There are only two issues:
when calling applyOnEachElement2 recursively, you switched the parameters
the f parameter is passed recursively but never actually used for anything
To fix this, all you need is to switch the order of parameters (I'll do this on the function arguments to get the parameters in the same order as standard map) and call the f function on hd on the last line (so that the function returns a list of transformed elements):
let rec applyOnEachElement2 f (list: Student list) =
match list with
| [] -> []
| hd :: tl -> (f hd)::applyOnEachElement2 f tl
You can also make it generic by dropping the type annotation, which gives you a function with the same type signature as the built in List.map:
let rec applyOnEachElement2 f list =
match list with
| [] -> []
| hd :: tl -> (f hd)::applyOnEachElement2 f tl

using if statement inside a foreach loop

I seem to always have problems with using 2 "end"'s in the same block of code for example:
Worker = fun (File) ->
{ok, Device} = file:read_file([File]),
Li = string:tokens(erlang:binary_to_list(Device), "\n"),
Check = string:join(Li, "\r\n"),
FindStr = string:str(Check, "yellow"),
if
FindStr > 1 -> io:fwrite("found");
true -> io:fwrite("not found")
end,
end,
message is "syntax error before: 'end'"
You need to remove the comma between to end's.
Worker = fun (File) ->
{ok, Device} = file:read_file([File]),
Li = string:tokens(erlang:binary_to_list(Device), "\n"),
Check = string:join(Li, "\r\n"),
FindStr = string:str(Check, "yellow"),
if
FindStr > 1 -> io:fwrite("found");
true -> io:fwrite("not found")
end
end,
The rule is simple - all 'statements' are to be preceded with a comma unless they
happen to be the last.
Your if expression is the last in the block (fun) passed to foreach. Which means it
does not need a trailing ,.
So
end
end,
is what you need. A simpler example:
L = [1, 2, 3, 4],
lists:foreach(
fun(X) ->
Y = 1,
if
X > 1 -> io:format("then greater than 1!~n");
true -> io:format("else...~n")
end
end,
L
)

Conditional sum in F#

I have defined a record type for some client data in F# as follows:-
type DataPoint = {
date: string;
dr: string;
Group: string;
Product: string;
Book: int;
Revenue: int} with
static member fromFile file =
file
|> File.ReadLines
|> Seq.skip 1 //skip the header
|> Seq.map (fun s-> s.Split ',') // split each line into array
|> Seq.map (fun a -> {date = string a.[0]; dr = string a.[1];
Group = string a.[2]; Product = string a.[3];
Book = int a.[4]; Revenue = int a.[5] });;
// creates a record for each line
let pivot (file) = DataPoint.fromFile file
|> ??????????
For the rows where date, dr, Group and Product are all equal, I want to then sum all of the Book and Revenue entries, producing a pivoted row. So some kind of if else statement should be fine. I suspect I need to start at the first data point and recursively add each matching row and then delete the matching row to avoid duplicates in the output.
Once I have done this I will be easily able to write these pivoted rows to another csv file.
Can anyone get me started?
Seq.groupBy and Seq.reduce are what you're looking for:
let pivot file =
DataPoint.fromFile file
|> Seq.groupBy (fun dp -> dp.date, dp.dr, dp.Group, dp.Product)
|> Seq.map (snd >> Seq.reduce (fun acc dp ->
{ date = acc.date; dr = acc.dr;
Group = acc.Group; Product = acc.Product;
Book = acc.Book + dp.Book;
Revenue = acc.Revenue + dp.Revenue; }))
Quickly hacked up, should give you some idea:
// Sample data
let data = [
{date = "2012-01-01"
dr = "Test"
Group = "A"
Product = "B"
Book = 123
Revenue = 123}
{date = "2012-01-01"
dr = "Test"
Group = "A"
Product = "B"
Book = 123
Revenue = 123}
{date = "2012-01-01"
dr = "Test"
Group = "B"
Product = "B"
Book = 11
Revenue = 123}]
let grouped = data |> Seq.groupBy(fun d -> (d.date, d.dr, d.Group, d.Product))
|> Seq.map (fun (k,v) -> (k, v |> Seq.sumBy (fun v -> v.Book), v |> Seq.sumBy (fun v -> v.Revenue)))
for g,books,revs in grouped do
printfn "Books %A: %d" g books
printfn "Revenues %A: %d" g revs
prints
Books ("2012-01-01", "Test", "A", "B"): 246
Revenues ("2012-01-01", "Test", "A", "B"): 246
Books ("2012-01-01", "Test", "B", "B"): 11
Revenues ("2012-01-01", "Test", "B", "B"): 11

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