Question

I am new to prolog and as a school assignment I need to write a DCG in Prolog and then write a perdicate to determine something. The input is a String which looks like this:

Date,Exchange,Tape A,Tape A %,Tape B,Tape B %,Tape C,Tape C %,Total,Total %,Tape A Moving,Tape A % Moving,Tape B Moving,Tape B % Moving,Tape C Moving,Tape C % Moving,Total Moving, Total % Moving
02/18/2014,NASDAQ,473515614,13.80%,119199766,11.40%,520888794,27.50%,1113604174,17.50%,511929756,14.30%,145320601,12.30%,532617511,26.30%,1189867868,17.50%
02/18/2014,NYSE,721710805,21.10%,0,0.00%,0,0.00%,721710805,11.40%,707472219,19.70%,0,0.00%,0,0.00%,707472219,10.40%

the first line is the header.

I wrote the DCG parser:

row([[Date,Stock,A,PerA,B,PerB,C,PerC,Total,PerTotal,A2,A3,B2,B3,C2,C3,T2,T3,'\n']|Rows]) -->
    date(Date), separe,stock(Stock),
    integer(A),separe,number(PerA),"%",separe,
    integer(B),separe,number(PerB),"%",separe,
    integer(C),separe,number(PerC),"%",separe,
    integer(Total),separe,number(PerTotal),"%",separe,
    integer(A2),separe,number(A3),"%",separe,
    integer(B2),separe,number(B3),"%",separe,
    integer(C2),separe,number(C3),"%",separe,
    integer(T2),separe,number(T3),"%",separe,
    row(Rows).

row(Rows) -->
    string(_),"\n" ,
    row(Rows).

row([]) --> [].

date(D/M/Y) -->
    integer(D), "/", integer(M), "/", integer(Y).

stock([F|Ks]) -->
    [F], {F \= 0' }, string(Ks).

separe --> white, whites.

it works Ok but after I parse the input I need to find the element with the maximum Total. I though of doing something like this:

findMaxTotal([],X,Ret).
findMaxTotal([R1|Rest],X,Ret):-
    nth1(9,R1,X),
    findMaxTotal(Rest,Y,max(Ret,X)).

but it doesn't work and I couldn't really debug it in SWI prolog to understand why.

Does anyone have any tips on how to find the max in the list of lists?

Thank you!

Was it helpful?

Solution

One problem in the original attempt is this query:

findMaxTotal(Rest,Y,max(Ret,X)).

Prolog does not evaluate arithmetic expressions when processing query arguments. The max/2 must appear, at some point, in the second argument of an is/2 or in an arithmetic comparator to be evaluated.

The fix-up of the original code, which is a classic Prolog list processing example, would be:

findMaxTotal([R|Rest], Ret) :-
    findMaxTotal(T, R, Ret).

findMaxTotal([], X, X).
findMaxTotal([R|Rest], X, Ret):-
    nth1(9, R, X1),
    Y is max(X, X1),
    findMaxTotal(Rest, Y, Ret).

This would also actually work written as follows:

findMaxTotal([R|Rest], Ret) :-
    findMaxTotal(T, R, Ret).

findMaxTotal([], X, Ret) :-
    Ret is X.   % Evaluate X

findMaxTotal([R|Rest], X, Ret):-
    nth1(9, R, X1),
    findMaxTotal(Rest, max(X, X1), Ret).

Here, in the body of the base case findMaxTotal([], X, Ret), Ret is finally evaluated from whatever X is, which might look something like, max(max(max(max(3, 5), 2), 7), 6). (There would be a max for each element of the original list.)

A list processing pattern such as this is ideal for the maplist predicate, and use of the predicate max_list/2, both of which are available in SWI and GNU Prolog (and perhaps others):

find_max_total(Data, ElemIndex, Max) :-
    maplist(nth1(ElemIndex), Data, Values),
    max_list(Values, Max).

And, in your case, use the query:

find_max_total(Data, 9, Max).

ADDENDUM

If what you need is to obtain the entire record which has the maximum value at a given column, the first recursive method is probably the most straightforward approach. But rather than just carry along the value, we carry along the whole record as well. In addition, we'll want an explicit maximum check so we know which record the maximum value came from:

find_max_record([Datum|Data], ElemIndex, MaxDatum) :-
    nth1(ElemIndex, Datum, Value),
    find_max_record(Data, Datum, Value, ElemIndex, MaxDatum).

find_max_record([], MaxDatum, _, _, MaxDatum).
find_max_record([Datum|Data], MaxDatumSoFar, MaxValueSoFar, ElemIndex, MaxDatum) :-
    nth1(ElemIndex, Datum, Value),
    (   Value > MaxValueSoFar
    ->  NewMaxDatum = Datum,
        NewMaxValue = Value
    ;   NewMaxDatum = MaxDatumSoFar,
        NewMaxValue = MaxValueSoFar
    ),
    find_max_record(Data, NewMaxDatum, NewMaxValue, ElemIndex, MaxDatum).

As an analog to the above maplist approach, but not as efficient, would be to "index" each row of the data data with the element we want to compare. A row such as, ['a', 5, test(a)] becomes 5-['a', 5, test(a)], and then we use maplist/3 to transform the entire list of data. Then we can apply a more general max_list_ex/2 (a max_list/2 we can write using the more general@` term comparators) to that indexed list of data and get the result.:

% Extended max_list/2 which does general term comparison
max_list_ex([H|T], M) :-
    max_list_ex(T, H, M).
max_list_ex([H|T], A, M) :-
    H @> A,
    max_list_ex(T, H, M).
max_list_ex([H|T], A, M) :-
    H @=< A,
    max_list_ex(T, A, M).
max_list_ex([], M, M).

% Maps an index and a Datum (a list) to Elem-Datum
index_elem(ElemIndex, Datum, Elem-Datum) :-
    nth1(ElemIndex, Datum, Elem).

find_max_record(Data, ElemIndex, MaxRec) :-
    maplist(index_elem(ElemIndex), Data, IndexedData),
    max_list_ex(IndexedData, _-MaxRec).

OTHER TIPS

I think that you can do this by using findall/3 and max_list/2. See example below. Row contains list of lists in which second element is a number. By using findall/3 that numbers are extracted into separate list and then maximum is found using max_list/2.

?- Row = [['a', 5, test(a)], ['b', 10, test(b)], ['c', -1, test(c)]], 
findall(Value, (member(SubList, Row), nth1(2, SubList, Value)), ListValues),
max_list(ListValues, Max).

Row = [[a, 5, test(a)], [b, 10, test(b)], [c, -1, test(c)]],
ListValues = [5, 10, -1],
Max = 10.

Here I enclose it in nice predicate:

findMaxInSubList(Data, ElemIndex, Max) :-
    findall(Value, (member(SubList, Data), nth1(ElemIndex, SubList, Value)), ListValues),
    max_list(ListValues, Max).

Sample input and output:

?- sample_data(Row), findMaxInSubList(Row, 2, Max).
Row = [[a, 5, test(a)], [b, 10, test(b)], [c, -1, test(c)]],
Max = 10.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top