Search code examples
prologprolog-findall

How to return a list of recommendations in Prolog?


For my assignment, I am supposed to list 20 potential pets and then define facts about each of the pets. Then I need to ask the potential pet owner five questions that will help decide which pets would be good recommendations. I am trying to return a list of pets based on the user input but it just returns true everytime and doesn't actually list out the recommended pets. Not sure where I am going wrong. I am only going to include some of the pets in my code example so its not terribly long.

pet_advisor.pl:

pet(cat).
pet(chameleon).
pet(chicken).
pet(chinchilla).
pet(cow).

size(cat, small).
sleeps(cat, night).
stays(cat, indoor).
stays(cat, outdoor).
class(cat, mammal).
live(cat, 12)

size(chameleon, small).
sleeps(chameleon, night).
stays(chameleon, indoor).
class(chameleon, reptile).
live(chameleon,5).

size(chicken, small).
sleeps(chicken, night).
stays(chicken, outdoor).
class(chicken, bird).
live(chicken,10).

size(chinchilla, small).
sleeps(chinchilla, day).
stays(chinchilla, indoor).
class(chinchilla, mammal).
live(chinchilla,15).

size(cow, large).
sleeps(cow, night).
stays(cow, outdoor).
class(cow, mammal).
live(cow,22).

pet_size_ok(X) :- pet_size(X), size(Y, X).
sleep_type_ok(X) :- sleep_type(X), sleeps(Y, X).
pet_location_ok(X) :- pet_location(X), stays(Y, X).
kind_ok(X) :- kind(X), class(Y, X).
life_ok(X) :- life(X), live(Y, Z), Z =< X.

which_pet(X) :- pet_size_ok(X), sleep_type_ok(X), pet_location_ok(X), kind_ok(X), life_ok(X).

recommend :- write('Do you want a small, medium, or large sized pet? '), read(Size), nl, assert(pet_size(Size)),
             write('Do you want a pet that sleeps during the day or night? '), read(Sleep), nl, assert(sleep_type(Sleep)),
             write('Do you want an indoor or outdoor pet? '), read(Place), nl, assert(pet_location(Place)),
             write('Do you want a reptile, mammal, bird, or a fish? '), read(Type), nl, assert(kind(Type)),
             write('How long do you want your pet to live (years)? '), read(Age), nl, assert(life(Age)),
             findall(Pets, which_pet(Pets), Suggestions),
             write('I would recommend these pets for you: '), nl, writelist(Suggestions),
             retract(pet_size(Size)), retract(sleep_type(Sleep)), 
             retract(pet_location(Place)),
             retract(kind(Type)), retract(life(Age)).

writelist([]).
writelist([H|T]) :- writeonce(H,T), writelist(T).  
writeonce(H,T) :- member(H,T).
writeonce(H,T) :- not(member(H,T)), write(H), nl.

So if I were to answer the questions like: small night indoor mammal 15

It should return a list with [cat, chinchilla], but all it returns is true.


Solution

  • There are several issues with your code. First, with most Prolog systems and in the Prolog standard, discontiguous predicate must be declared. Add the following directive in the beginning of your file:

    :- discontiguous([
        size/2, sleeps/2, stays/2, class/2, live/2
    ]).
    

    Next, there's no need for using dynamic predicates and asserting and retracting facts which time you query for a recommendation:

    which_pet(Size, Sleep, Place, Type, Age, Pet) :-
        size(Pet, Size),
        sleeps(Pet, Sleep),
        stays(Pet, Place),
        class(Pet, Type),
        live(Pet, Age0), Age0 =< Age.
    
    recommend :-
        write('Do you want a small, medium, or large sized pet? '), read(Size), nl,
        write('Do you want a pet that sleeps during the day or night? '), read(Sleep),
        write('Do you want an indoor or outdoor pet? '), read(Place), nl,
        write('Do you want a reptile, mammal, bird, or a fish? '), read(Type), nl,
        write('How long do you want your pet to live (years)? '), read(Age), nl,
        findall(Pet, which_pet(Size,Sleep,Place,Type,Age,Pet), Suggestions),
        write('I would recommend these pets for you: '), nl, writelist(Suggestions).
    

    This is not an ideal rewrite as it scales poorly but is much better than using dynamic predicates.

    As an ending remark, your code for printing the results does two tasks that would be better separated: (1) filter duplicates and (2) print the unique results. I recommend that you separate these tasks. filtering the results can be done e.g. by using setof/2 instead of findall/3 or by calling sort/2 on the list constructed by the findall/3 call. I leave that rewrite to you. Also use the standard negation control construct, \+/1, instead of the legacy/deprecated not/1 predicate.

    Sample call:

    | ?- recommend.
    Do you want a small, medium, or large sized pet? small.
    Do you want a pet that sleeps during the day or night? night.
    Do you want an indoor or outdoor pet? outdoor.
    Do you want a reptile, mammal, bird, or a fish? bird.
    
    How long do you want your pet to live (years)? 20.
    
    I would recommend these pets for you: 
    chicken
    
    yes