Search code examples
prologprolog-setof

Prolog - Using Bagof


I've been stuck on a past paper question while studying for my exams.

The question is:

https://gyazo.com/ee2fcd88d67068e8cf7d478a98f486a0

I figured I've got to use findall/bagof/setof because I need to collect a set of solutions. Furthermore, setof seems appropriate because the list needs to be presented in descending order.

My solution so far is:

teams(List) :- 
    setof((Team, A), 
    (Team^team(Team, _, Wins, Draws, _), A is Wins*3 + Draws*1), 
    List).

However the problem is I don't quite get the answers all in one list. I'm very likely using Team^ incorrectly. I'd really appreciate pointers on how I can get a list of ordered tuples in terms of points. The output it gives me is:

X = [(queenspark,43)] ? ;
X = [(stirling,26)] ? ;
X = [(clyde,25)] ? ;
X = [(peterhead,35)] ? ;
X = [(rangers,63)] ? ;

Also, it's not really apparent what kind of order, if any it's in, so I'm also lost as to how setof is ordering.

Whats the best way to approach this question using setof?

Thanks.


Solution

  • Firstly, I would suggest to change (Team,A) to a pair representation A-Team with the A being in front since this is the total score of the team and thus the key you want to use for sorting. Then you would like to prefix the variables that should not be in the list with a ^ in front of the query you want to aggregate. See the following example:

       ?- setof(A-Team, P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1), List).
    List = [25-clyde,26-stirling,35-peterhead,43-queenspark,63-rangers]
    

    Since you asked, consider the following query with the pair ordering flipped to Team-A for comparison reasons:

       ?- setof(Team-A,P^Wins^Draws^L^(team(Team,P,Wins,Draws,L), A is Wins*3 + Draws*1),List).
    List = [clyde-25,peterhead-35,queenspark-43,rangers-63,stirling-26]
    

    Now the resulting list is sorted with respect to the teamnames. So A-Team is the opportune choice. You could then use the predicate lists:reverse/2 to reverse the order to a descending list and then define an auxilary predicate pair_second/2 that you can use with apply:maplist/3 to get rid of the leading scores in the pairs:

    :- use_module(library(lists)).
    :- use_module(library(apply)).
    
    % team(+Name, +Played, +Won, +Drawn, +Lost)
    team(clyde,26,7,4,15).
    team(peterhead,26,9,8,9).
    team(queenspark,24,12,7,5).
    team(rangers,26,19,6,1).
    team(stirling,25,7,5,13).
    
    pair_second(A-B,B).    % 2nd argument is 2nd element of pair
    
    teams(Results) :- 
       setof(A-Team, 
             P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1), 
             List),
       reverse(List,RList),
       maplist(pair_second,RList,Results). % apply pair_second/2 to RList
    

    If you query the predicate now you get the desired results:

       ?- teams(T).
    T = [rangers,queenspark,peterhead,stirling,clyde]
    

    Concerning your question in the comments: Yes, of course that is possible. You can write a predicate that describes a relation between a list of pairs and a list than only consists of the second element of the pairs. Let's call it pairlist_namelist/2:

    pairlist_namelist([],[]).
    pairlist_namelist([S-N|SNs],[N|Ns]) :-
       pairlist_namelist(SNs,Ns).
    

    Then you can define teams/1 like so:

    teams(Results) :- 
       setof(A-Team, 
             P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1), 
             List),
       reverse(List,RList),
       pairlist_namelist(RList,Results).
    

    In this case, besides maplist/3, you don't need pair_second/2 either. Also you don't need to include :- use_module(library(apply)). The example query above yields the same result with this version.