Sorting predicates in PROLOG

Thank you so much the function works perfectly.
I want to change the name of predicates from p to patient.
But i’m getting wrong results (redundant results):
I changed the p in the predicates and in the functions :


patient( 301, 1, 2 ).
patient( 201, 5, 2 ).
patient( 501, 1, 5 ).
patient( 401, 1, 4 ).

key_value(k(C,NegB), patient(A,B,C)) :-
    patient(A, B, C),
    NegB is -B.

p_sort(PairsUnsorted, List) :-
    bagof(K-P, key_value(K, P), PairsUnsorted),
    keysort(PairsUnsorted, PairsSorted),
    pairs_values(PairsSorted, List).

Am I missing anything in the function ?

Thanks

EDIT :

I think it’s because i was adding
:- dynamic patient/3., when i removed it, it worked fine.

I don’t see why adding the dynamic directive would do anything. Perhaps if you show a full session, and also the file(s) that you used, I could explain why you’re getting “redundant” results.

You were right.
I reloaded the file, and everything works fine, the dynamic was not the problem

Can also use order_by.

1 Like

how about displaying the result like this :

201 5 2
301 1 2
401 1 4
501 1 5

What can i add to get this format ?

Use format/2.

If you use sort/4 you only need:

?- findall(p(X,Y,Z), p(X,Y,Z), Ps), sort(3, @=<, Ps, Result).
Result = [p(301, 1, 2), p(201, 5, 2), p(401, 1, 4), p(501, 1, 5)].

If you want to format it maybe forall/2 and order_by/2?

?- forall(order_by([asc(Z)], p(X,Y,Z)), format("~w ~w ~w~n", [X,Y,Z])).
301 1 2
201 5 2
401 1 4
501 1 5
true.

How can i split the Sorted list into list of lists depending on the 3rd argument of p predicate.
I mean getting something like :

Sorted = [ [p(201,5,2),p(301,1,2) ] , [p(401,1,4)] , [p(501,1,5)] ].

How about this ? Sort by 3rd arguments with sort/3, then do grouping the sorted list by the 3rd arguments. Also the following seems to work, though I prefer the former.

% ?- L = [p(2,2,1), p(1,1,1), p(3,3,2)], findall(I, (member(A, L), arg(3, A, I)), Is), sort(Is, Js),
% findall(S, (member(J, Js), findall(P, (member(P, L), arg(3, P, J)), S)), T).
%@ L = [p(2, 2, 1), p(1, 1, 1), p(3, 3, 2)],
%@ Is = [1, 1, 2],
%@ Js = [1, 2],
%@ T = [[p(2, 2, 1), p(1, 1, 1)], [p(3, 3, 2)]].

An old school approach, ignoring the facilities of library(solution_sequences):

group_lists(L) :-
    setof(C,X^Y^p(X,Y,C),Cs),
    findall(S,(member(C,Cs),setof(p(A,B,C),p(A,B,C),S)),L).

edit

Just to showcase an application of distinct/2

group_lists(Ls) :-
    findall(L,(
      distinct(C,p(_,_,C)),
      setof(p(A,B,C),p(A,B,C),L)
    ),Ls).

Could be interesting to benchmark these 2 approaches.

edit

My benchmark shows the first approach is more efficient, even if the second one doesn’t sort the primary sequence… on 1.000.000 elements I get

?- benchmark.
% 2,000,106 inferences, 1.000 CPU in 1.062 seconds (94% CPU, 2000106 Lips)
% 10,000,113 inferences, 1.469 CPU in 1.496 seconds (98% CPU, 6808588 Lips)

This is perfect. My solution ignored that p/3 is a predicate, not a term.

I don’t really like the findall/setof/… solutions for two reasons. First of all, if something unexpectedly fails you typically loose part of the answer without any feedback and second because it involves copying. Copying ground terms is fine (just costs memory and time), but for non-ground terms it already gets harder and for terms with constraints it quickly leads to incorrect results. I’d use this instead:

group_by_arg3(Terms, Grouped) :-
    map_list_to_pairs(arg(3), Terms, Keyed),
    keysort(Keyed, Sorted),
    group_pairs_by_key(Sorted, KeyGrouped),
    pairs_values(KeyGrouped, Grouped).

See SWISH -- SWI-Prolog for SHaring

That is the solution using library predicates. Alternatively, use sort/4 to sort the list on the 3th argument and do the splitting by hand. That is faster, but requires more writing.

At a more general level, Prolog programs handling this type of data as lists and doing such operations on them smells dubious. It could be fine. It could also be you are not using Prolog as a logic programming language but merely as an imperative data processing language. Prolog can do that, but the advantage compared to modern imperative languages is small. To judge though, one needs a much more complete picture about the problem you are trying to solve.

2 Likes

Assuming findall(X, cond(X), S) means mathematically that S = { X | cond(X) }, if cond(X) is constraint-free, the S should be clear. However cond(X) contains constraints then as
S must inherit the suspended constraints. I feel such S like a monster which is almost impossible to be controled completely. So my use of “complete” should have been used for constraint-free condition cond(X). I could agree with most of Jan’s comment, but I like setof/3, findall/3 and become to use more frequently. BTW, I have not custom to use constraints.

Implemented as:

test_sort_group(Group) :-
    Patients = [patient(204,4,2),patient(203,3,2),patient(304,8,3),patient(303,7,3),patient(404,12,4),patient(403,11,4),patient(504,16,5),patient(503,15,5),patient(1,1,1),patient(506,6,5)],
    group_list_by_arg(3, Patients, Group).


group_list_by_arg(Arg, Lst, Group) :-
    % Keeping duplicates
    sort(Arg, @=<, Lst, [H|T]),
    group_list_by_arg_main_(T, Arg, H, Group).

group_list_by_arg_main_(T, Arg, H, Group) :-
    arg(Arg, H, ArgVal),
    Upto = [H|Upto0],
    group_list_by_arg_loop_(T, Arg, ArgVal, Upto0, Rem),
    group_list_by_arg_rem_(Rem, Arg, Upto, Group).

% Reached end of groups
group_list_by_arg_rem_([], _, G, G).
group_list_by_arg_rem_([_|_], _, G, G).
group_list_by_arg_rem_([H|T], Arg, _Upto, Group) :-
    % Assemble next group
    group_list_by_arg_main_(T, Arg, H, Group).

% Reached end of sorted list
group_list_by_arg_loop_([], _, _, [], []).
group_list_by_arg_loop_([H|T], Arg, ArgVal, Group, Rem) :-
    arg(Arg, H, ArgVal), !,
    % Add element to current group
    Group = [H|Group0],
    group_list_by_arg_loop_(T, Arg, ArgVal, Group0, Rem).
% Finished this group, but there may be other elements
group_list_by_arg_loop_([H|T], _Arg, _ArgVal, [], [H|T]).
?- time(bagof(G, test_sort_group(G), Gs)).
% 49 inferences, 0.000 CPU in 0.000 seconds (94% CPU, 599807 Lips)
Gs = [[patient(1,1,1)],[patient(204,4,2),patient(203,3,2)],[patient(304,8,3),patient(303,7,3)],[patient(404,12,4),patient(403,11,4)],[patient(504,16,5),patient(503,15,5),patient(506,6,5)]].
1 Like

I try to create an emergency system to find people on time and those late.
so my list now looks like:

[ [patient(204,4,2),patient(203,3,2)] , [patient(303,7,3),patient(302,6,3)] , [patient(404,12,4) ,patient(403,11,4)] , [patient(504,16,5),patient(503,15,5)] ]

I want for each list to find those on time using the following function:

(consultation time) * (index of patient) + (2nd argument of each patient)

consultation time is constant:
example :
consultTime(15)

[ [4 , 18] , [37, 51] , [72, 86] , [106, 120] ]

the problem is with the indices how can i preserve the same index as with a normal list i.e.:

[ [0, 1], [2, 3], [4, 5] ]

The more logic based solution is to have a database of patients, e.g.

patient(204, 4, 2).
patient(203, 3, 2).

and next define e.g.

patient_on_time(Patient) :-
    ...

patient_late(Patient) :-
    patient(Patient),
    \+ patient_on_time(Patient).

etc. That way you can define complex rules on patients you want to retrieve that are easy to read. In the end, if you need the set of patients that satisfy some condition you use setof/3 or you use one of the other aggregation predicates to do things such as counting, getting averages, (sorted) sets of solutions, etc.

2 Likes

Thanks so much !

I managed to find the patients on time and late,
there I have a list of patients with their states:

ListP = [patient(201, 4, 2, inTime), patient(202, 3, 2, late), patient(203, 2, 3, late), patient(204, 1, 3, late), patient(204 , 1, 4, inTime),patient(203, 2, 4, late), patient(204, 1, 5, inTime),patient(204, 1, 5, inTime)].

I want to have the number of patients in time for the patients classified by their 3rd argument

in the previous example I have
patient(201, 4, 2, inTime), patient(202, 3, 2, late) so I have only one patient in time and who has as 3rd argument 2, the same thing for other patients and so on…

The result list for the previous example should be:

ResList = [1, 0, 1, 2].

If the data are in a list, you can make them appear to be like a predicate by using member/3, e.g.:

patient(P) :-
    member(P, [patient(201, 4, 2, inTime), 
               patient(202, 3, 2, late), 
               patient(203, 2, 3, late), 
               patient(204, 1, 3, late), 
               patient(204, 1, 4, inTime),
               patient(203, 2, 4, late),
               patient(204, 1, 5, inTime),
               patient(204, 1, 5, inTime)]).

my actual data now is in a list as showen in the last exemple :

ListP = [patient(201, 4, 2, inTime), patient(202, 3, 2, late), patient(203, 2, 3, late), patient(204, 1, 3, late), patient(204 , 1, 4, inTime),patient(203, 2, 4, late), patient(204, 1, 5, inTime),patient(204, 1, 5, inTime)].

I edited my text hoping my problem will be more clear.

Isn’t possible to do it with lists ? i shoul make them appear to be like a predicate ?

what i tried :

count_occurrences(List, Occ):-
    findall([patient(_,_,X,aTemps),L], (bagof(true,member(patient(_,_,X,aTemps),List),Xs), length(Xs,L)), Occ).

How can i add the index of each line of the list :

1 301 1 2
2 201 5 2
3 401 1 4
4 501 1 5