Count number of occurrence in a list with condition

I’m using: SWI-Prolog version 8.4.3 for x64-win64

i have a list of predicates :

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)
].

and i want to count number of patients inTime categorized by their 3rd argument:

I want the result to be something like :

Occ = [
   [patient(_, _, 2, inTime), 1],
   [patient(_, _, 3, inTime), 0],
   [patient(_, _, 4, inTime), 1],
   [patient(_, _, 5, inTime), 2]
].

What i tried :

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

The result of my code :

Occ = [
   [patient(_, _, 2, inTime), 1],
   [patient(_, _, 4, inTime), 1],
   [patient(_, _, 5, inTime), 2]
].

it works well except that it does not give me the result for the patient whose 3rd argument is 3
[patient(_, _, 3, inTime), 0] is omitted.

Thanks for your help

Use Sorting predicates in PROLOG - #19 by brebs and adapt it to count.

It happens because bagof/3 fails if it cannot find any answer (in contrast, findall/3 succeeds with an empty list).
It’s not easy to fix your code, because you’re using bagof to produce the distinct values. Maybe

count_occurrences(ListP,CountInTime) :-
    map_list_to_pairs(arg(3),ListP,Keyed),
    group_pairs_by_key(Keyed,Grouped),
    findall([patient(_,_,K,inTime),N],
            (   member(K-L,Grouped),
                aggregate_all(count,member(patient(_,_,K,inTime),L),N)
            ),CountInTime).
1 Like

OMG, Yous saved my life ! Thanks so much.
̶C̶a̶n̶ ̶i̶ ̶u̶s̶e̶ ̶t̶h̶e̶ ̶s̶a̶m̶e̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶t̶o̶ ̶c̶o̶u̶n̶t̶ ̶n̶u̶m̶b̶e̶r̶ ̶o̶f̶ ̶p̶r̶e̶d̶i̶c̶a̶t̶e̶s̶,̶ ̶c̶l̶a̶s̶s̶i̶f̶i̶e̶d̶ ̶b̶y̶ ̶t̶h̶e̶i̶r̶ ̶3̶r̶d̶ ̶a̶r̶g̶u̶m̶e̶n̶t̶.̶

̶t̶o̶ ̶g̶e̶t̶ ̶s̶o̶m̶e̶t̶h̶i̶n̶g̶ ̶L̶i̶k̶e̶ ̶:̶

Occ = [
   [patient(_, _, 2, _), 2],
   [patient(_, _, 3, _), 2],
   [patient(_, _, 4, _), 2],
   [patient(_, _, 5, _), 2]
].

SOLVED :

I just changed the inTime in the code to a _

count_occurrences_all(ListP,CountInTime) :-
    map_list_to_pairs(arg(3),ListP,Keyed),
    group_pairs_by_key(Keyed,Grouped),
    findall([patient(_,_,K,_),N],
            (   member(K-L,Grouped),
                aggregate_all(count,member(patient(_,_,K,_),L),N)
            ),CountInTime).

If don’t need to discriminate by inTime, then could be a bit easier:

count_occurrences_all(ListP,Counted) :-
    map_list_to_pairs(arg(3),ListP,Keyed),
    group_pairs_by_key(Keyed,Grouped),
    findall([patient(_,_,K,_),N],
            (   member(K-L,Grouped),
                length(L,N)
            ),Counted).

or

count_occurrences_all(ListP,Counted) :-
    map_list_to_pairs(arg(3),ListP,Keyed),
    group_pairs_by_key(Keyed,Grouped),
    maplist([K-L,[patient(_,_,K,_),N]]>>length(L,N),Grouped,Counted).

Always try to keep the logic as simple as possible…

1 Like

How can i iterate throught both lists so i can make operation between the second element of each sublist :

Occ = [
   [patient(_, _, 2, inTime), 1],
   [patient(_, _, 3, inTime), 0],
   [patient(_, _, 4, inTime), 1],
   [patient(_, _, 5, inTime), 2]
].

Occ_all = [
   [patient(_, _, 2, _), 2],
   [patient(_, _, 3, _), 2],
   [patient(_, _, 4, _), 2],
   [patient(_, _, 5, _), 2]
].

For exemple division:

1/2
0/2
1/2
2/2

To get the following list :
ResList=[ 0.5, 0, 0.5, 1].

maplist/4 … something like this:

div([_,X], [_,Y], Z) :- Z is X/2.
?- maplist(div, Occ, Occ_all, Result).