user10876930
user10876930

Reputation: 55

How Do I Compare a List to a Database Prolog

I have a database of facts that has entries like this snippet

symptom(shingles,headache).    
symptom(shingles,fever).    
symptom(shingles,malaise).    
symptom(shingles,headache).    
symptom(shingles,itching).    
symptom(shingles,hyperesthesia).    
symptom(shingles,paresthesia).   

test(shingles,blood).    
test(shingles,pcr).   

locale(shingles,all).  

treatment(shingles,calamine).    
treatment(shingles,aciclovir).

treatment(shingles,valaciclovir).    
treatment(shingles,famciclovir).    
treatment(shingles,corticosteroids).

I then have a predicate that gets a list of symptoms from the user.

getSymptoms(Symptoms) :-
    write('Please enter symptoms now, enter "Done" when finished: ' ),
    read_string(user, "\n", "\r", _, Response),
    (
        Response == "Done"
    ->
        Symptoms = []
    ;
        getSymptoms(Symptoms0),
        Symptoms = [Response|Symptoms0]
    ).

My question is how do I compare the lists of user symptoms to the symptoms fact second atom, and then add the disease to another list? For example, the user enters fever. Since fever is in the symptom fact for shingles, it would add shingles to a list.

Upvotes: 1

Views: 152

Answers (1)

Guy Coder
Guy Coder

Reputation: 24976

This will work, but allows for duplicate symptoms to be entered. I am posting it now so you can see the first part of the transformation and will post the part without the duplicates when I get that working.

getSymptoms(Symptoms) :-
    write('Please enter symptoms now, enter "Done" when finished: ' ),
    read_string(user, "\n", "\r", _, Response),
    (
        Response == "Done"
    ->
        Symptoms = []
    ;
        atom_string(Symptom,Response),
        valid_symptom(Symptom,Symptoms)
    ).

valid_symptom(Symptom,Symptoms) :-
    (
        symptom(_,Symptom)
    ->
        % Symptom was valid
        % so get next symptom and
        % add to list on backtracking
        getSymptoms(Symptoms0),
        Symptoms = [Symptom|Symptoms0]
    ;
        % Symptom was invalid
        % so warn user of invalid symptom and what they input
        % and get next symptom.
        % Do not add invalid Symptom to list on backtracking.
        format('Invalid symptom: `~w''~n',[Symptom]),
        getSymptoms(Symptoms0),
        Symptoms = Symptoms0
    ).

Since the values entered are strings and the symptoms are atoms in symptom/2 facts, the input needs to be converted to an atom for comparison. This is done using atom_string/2

atom_string(Symptom,Response) 

To give a user feedback if the symptom is invalid format/2 is used. It is better to use than write/1 since it gives you more control over the output.

format('Invalid symptom: `~w''~n',[Symptom])

If an invalid value is entered for as a symptom it should not be added to the list. This is a classic if/then type of scenario and in Prolog is done with ->/2. Here is the standard template

(
    <conditional>
->
    <true branch>
;
    <false branch>
)

the conditional is

symptom(_,Symptom)

Notice also with the conditional, that it reads the symptom/2 facts and ignores the first part of the compound structure, i.e. _, and matches the input symptom with a symptom in the facts. This is were the comparison is done, but it is done via unification and not with a compare predicate such as ==/2.

The true branch is the same as before

getSymptoms(Symptoms0),
Symptoms = [Symptom|Symptoms0]

however the false branch is

format('Invalid symptom: `~w''~n',[Symptom]),
getSymptoms(Symptoms0),
Symptoms = Symptoms0

Notice that the invalid Symptom is not added to the list with [Symptom|Symptoms0] and also that both branches (true and false) should update the same variables, Symptoms, which in the false branch is done with Symptoms = Symptoms0 which is not assignment but =/2 (unification).

The code for valid_symptom/2 could have been inlined with getSymptoms/1 but I pulled it out so you could see how it is done in case you need to do that in the future.

Example run:

?- getSymptoms(Symptoms).
Please enter symptoms now, enter "Done" when finished: wrong
Invalid symptom: `wrong'
Please enter symptoms now, enter "Done" when finished: headache
Please enter symptoms now, enter "Done" when finished: malaise
Please enter symptoms now, enter "Done" when finished: headache
Please enter symptoms now, enter "Done" when finished: Done
Symptoms = [headache, malaise, headache].

Here is the next variation that removes duplicates while building the list.

getSymptoms(Result) :-
    getSymptoms_helper([],Result).

getSymptoms_helper(Symptoms,Result) :-
    write('Please enter symptoms now, enter "Done" when finished: ' ),
    read_string(user, "\n", "\r", _, Response),
    (
        Response == "Done"
    ->
        Result = Symptoms
    ;
        atom_string(Symptom,Response),
        valid_symptom(Symptom,Symptoms,Result)
    ).

valid_symptom(Symptom,Symptoms,Result) :-
    (
        memberchk(Symptom,Symptoms)
    ->
        % Symptom was a duplicate
        % Do not add duplicate Symptom to list.
        getSymptoms_helper(Symptoms,Result)
    ;
        (
            symptom(_,Symptom)
        ->
            % Symptom was valid
            % so get next symptom and
            % add to list.
            getSymptoms_helper([Symptom|Symptoms],Result)
        ;
            % Symptom was invalid
            % so warn user of invalid symptom and what they input
            % and get next symptom.
            % Do not add invalid Symptom to list.
            format('Invalid symptom: `~w''~n',[Symptom]),
            getSymptoms_helper(Symptoms,Result)
        )
    ).

The major change here is that an accumulator Symptoms is threaded through the predicates so that the list of valid symptoms can be built and used for testing the next input value. Since the accumulator needs to be initialized at the start, the previous predicate renamed to getSymptoms_helper so that the accumulator can be initialized with

getSymptoms_helper([],Result)

Notice [] that passes to

getSymptoms_helper(Symptoms,Result)

thus setting the initial value of Symptoms to [].

When Done is entered, the list is unified with Result and passed back upon back-chaining. Normally the variables would be named Symptoms0 and Symptoms but I kept them this way so they are easier to follow. Other wise there would be variables Symptom, Symptoms and Symptoms0, but once you get use to them are easier to follow.

The check for duplicates is done using memberchk/2 which is better than using member/2 for checking. Again this adds another conditional to the mix.

Example run:

?- getSymptoms(Symptoms).
Please enter symptoms now, enter "Done" when finished: headache
Please enter symptoms now, enter "Done" when finished: malaise
Please enter symptoms now, enter "Done" when finished: headache
Please enter symptoms now, enter "Done" when finished: Done
Symptoms = [malaise, headache].

Upvotes: 1

Related Questions