Search code examples
prologprolog-findall

Finding all solutions in prolog


In prolog I'm trying to unify every valid pairing of needs with resources

needs([ece2090,1,m,13,16]).
needs([ece3520,1,tu,11,14]).
needs([ece4420,1,w,13,16]).

resources([joel, [ece2090,ece2010,ece3520,ece4420],[[m,13,16]]]).
resources([sam, [ece2010,ece4420],[]]).
resources([pete, [ece3520],[[w,13,16]]]).

using this formula

make_bid([Class,Sect,Day,Ts,Te],[Name,Cap,Unavail],[Class,Sect,Day,Ts,Te,Name,_]) :-
no_conflict_all_unavailable(Day,Ts,Te,Unavail),
course_capable(Class,Cap),
writef('%w %w %w\n',[Class,Sect,Name]),
fail.

and running this test.

test(Listing) :- needs(N), resources(R), make_bid(N,R,Listing).

The point of this part of the program is to pair every class with a teacher that both has the qualifications to teach the class and is not unavailable during that time. It's supposed to give a list.

?- test(Listing).
ece3520 1 joel
ece3520 1 pete
ece4420 1 joel
ece4420 1 sam
false.

When run, the above is generated. This is correct, but it's in a format that's useless to me, since I need it to be a variable of its own to do further computations. Then the solution is to use bagof or findall, right?

So I remove the fail clause from the main part of the program and then change the test to this

test(Bag) :- needs(N), resources(R), bagof(Listing,make_bid(N,R,Listing),Bag).

but it generates this

ece3520 1 joel
Bag = [[ece3520, 1, tu, 11, 14, joel, _G4310]] 

If you look closely you'll see that there's no period at the end as well as a lack of a true/false statement. This would lead one to believe it is infinitely looping. This isn't the case however, as the Bag matrix is fully formed and I can simply type "." to end the program (instead of, you know, aborting it).

It only generates the first valid solution. Why is this happening?


Solution

  • You've structured your test predicate so that bagof/3 is called for every instance combination of needs(N) and resources(R) and so it collects each result of make_bid in it's own bagof/3 result:

    ece3520 1 joel
    Bag = [[ece3520, 1, tu, 11, 14, joel, _G4310]]
    

    The first line is the write that is in make_bid predicate. The second line is the Bag result for the single query to make_bid for one pair of needs/resources. The last argument in the list, _G4310, occurs because your predicate uses _ and it's anonymous (never used/instantiated).

    Your current make_bid is designed to write the results in a loop rather than instantiate them in multiple backtracks. So that could be changed to:

    make_bid([Class, Sect, Day, Ts, Te], [Name, Cap, Unavail], [Class, Sect, Day, Ts, Te, Name, _]) :-
        no_conflict_all_unavailable(Day, Ts, Te, Unavail),
        course_capable(Class, Cap).
    

    (NOTE: I'm not sure why you have _ at the end of the 3rd list argument. What does it represent?)

    If you want to collect the whole result in one list, then you canb use findall/3:

    findall([Class, Sect, Name], (needs(N), resources(R), make_bid(N, R, [Class, Sect, _, _, _, Name, _]), Listings).
    

    This will collect a list of elements that look like, [Class, Sect, Name]. You could use bagof/3 here, but you'd need an existential quantifier for the variables in the make_bid/3 call that you don't want to bind.

    If you wanted the entire Listing list, then:

    findall(L, (needs(N), resources(R), make_bid(N, R, L)), Listings).
    

    But each element of Listings will be a list whose last element is an anonymous variable, since that's how make_bid/3 is structured.