Question

Some days ago I asked a question about basis input/output in Prolog, but now I have another question.

I'm doing a Prolog project in my college related with an ITS diagnostic.

Let's say I started with some basic facts:

lesion(herpes).
lesion(sifilis).
ampolla(herpes).
inguinal(herpes).
fiber(herpes).
headache(herpes).
picazon(herpes).
secrecion(sifilis).

And I have some basic rules for testing:

its(herpes):-lesion(herpes), inguinal(herpes), picazon(herpes), fiber(herpes).
its(herpes):- ampolla(herpes), headache(herpes), picazon(herpes).
its(sifilis):-secrecion(sifilis), lesion(sifilis).

My question is:

Is there any way I can save each argument by using questions instead of set them by myself? Let me explain:

For example, I would like to declare an empty fact like: lesion(_) and then ask the user: Do you have any lesion? If the answer is yes, then the fact would be: lesion(herpes).

After that, I would ask another questions until get all facts with an argument so I could print: X= herpes or something like that.

Is that even possible?

Any help will be appreciate.

Was it helpful?

Solution

Edit: I have realized that this doesn't answer the question you asked, but instead proposes an alternative approach to the problem.


What you're trying to accomplish is easily achieved in Prolog, but I think it might make sense to rework your approach from the ground up.

Prolog syntax is descended from first order predicate calculus. Accordingly, the standard reading for a single place predicate of the form p(s) is "s is p". The functor, p, is treated as a predicate ascribing some quality or attribute to s. A fairly commonsense extension of this approach is to read predicates with a higher arity -- i.e., more arguments -- as attributions of relations between things, so r(a,b) can be read "a stands in the relation r to b".

Under this common reading, your facts would say "herpes is a lesion, syphilis is a lesion, herpes is an ampolla, etc..." which doesn't make much sense. More importantly, your current code doesn't define any relations whatsoever, and so is unable to derive any interesting truths through unification.

Your rules for its/1 exhibit a similarly unusual, and, perhaps, misguided approach (not to say that unusual = misguided). Generally we approach each predicate as expressing an elementary statement. If I want to state the fact that Socrates is mortal, I write mortal(socrates) including both the subject and the predicate. Likewise, if I want to express the relational fact that Socrates teaches Plato, I write teaches(socrates, plato) including both of the relata and the functor teaches that characterizes the relation. I read its(herpes) as "it is herpes", but it's not clear what 'it' refers to and there isn't any information on the kind of relation or property being ascribed to herpes.

I'll suggest another approach to your program, after which I offer some code to achieve the desired input/output interface.

I think your facts mean to describe symptoms of a disease. I would make this explicit using the predicate symptom/2 so that symptom(Symptom, Disease) means "Symptom is a symptom of the disease Disease":

symptom(lesion, herpes).
symptom(lesion, sifilis).
symptom(ampolla, herpes).
symptom(inguinal, herpes).
symptom(fiber, herpes).
symptom(headache, herpes).
symptom(picazon, herpes).
symptom(secrecion,sifilis).

Note that this set of facts already lets us extract useful information, in a way that your facts do not. For instance, we can find out all the symptoms of a disease by querying with a free variable for the Symptom argument:

?- symptom(X, herpes).
X = lesion ;
X = ampolla ;
X = inguinal ;
X = fiber ;
X = headache ;
X = picazon.

or we can find out which diseases might be indicated by a particular symptom by querying with a free variable in the Disease argument:

?- symptom(inguinal, X).
X = herpes.

Turning to your rules, I think that they are trying to say that a person with such-and-such symptoms can be diagnosed with such-and-such a disease. I might write it thus:

%% indicate_disease(+Symptoms:List, ?Disease:Atom)
%
%       True if all symptoms in the list Symptoms are symptoms of the disease Disease.
%

indicate_disease(Symptoms, Disease) :-
    foreach( member(Symptom, Symptoms), symptom(Symptom, Disease) ).

I read the rule to say, "the symptoms in the list Symptoms indicate the disease Disease if, for each Symptom which is a member of the list Symptoms, Symptom is a symptom of the disease Disease". When my code ends up reading like a horribly tedious, totally redundant statement, I generally feel that I am on the right track.

Of course, if you need to have only particular sets of symptoms indicate diseases, you'll need to make the rule less general. You can do this in any number of ways. For instance, you might write the predicate indicate_disease/2 to include the specific assortment of symptoms:

indicate_disease(Symptoms, herpes) :-
    Symptoms = [lesion, inguinal, picazon, fiber].

Or you might instead write an intermediary predicate which describes particular sets of symptom-disease relations sufficient for indicating a diagnosis:

indications_of(sifilis, [secrecion, lesion]).

and then writing indicate_disease/2 to use this predicate:

indicate_disease(Symptoms, Disease) :-
    indications_of(Disease, Symptoms).

With some code like this, we can now "diagnose" (we pretend) just by querying in this fashion:

?- indicate_disease([lesion, inguinal, picazon, fiber], Disease).
Disease = herpes.

Now your question comes into play: how to collect the symptoms as input from the user? If you're just using the terminal for your interface, the following pattern should suffice:

inquire_about_symptoms(Symptoms) :-
    user_has_symptoms(Symptoms, [], HasSymptoms),
    ( indicate_disease(HasSymptoms, Disease)
    ->  format('You have ~w -- :(~n', [Disease])
    ;   writeln('You are disease free! :)')
    ).

user_has_symptoms([], HasSymptoms, HasSymptoms).
user_has_symptoms([S|Symptoms], AccSymptoms, HasSymptoms) :-
    user_has_symptom(S, Answer),
    ( Answer == yes
    ->  NewAccSymptoms = [S|AccSymptoms]
    ;
      Answer == no
    ->  NewAccSymptoms = AccSymptoms
    ),
    user_has_symptoms(Symptoms, NewAccSymptoms, HasSymptoms).

user_has_symptom(Symptom, Answer) :-
    format('Do you have any ~w?~nAnswer "yes" or "no": ', [Symptom]),
    read(Answer).

Usage like so:

?- inquire_about_symptoms([lesion, inguinal, picazon, fiber]).
Do you have any lesion?
Answer "yes" or "no": yes.
Do you have any inguinal?
Answer "yes" or "no": yes.
Do you have any picazon?
Answer "yes" or "no": yes.
Do you have any fiber?
Answer "yes" or "no": yes.
You have herpes -- :(

Edit: Added to address follow up question in comments.

You can allow for the symptoms to be matched against a set of indications in any order by separating out the facts about which symptom-sets indicate a disease from the rule which checks whether some arbitrary set of symptoms is a disease. Then you simply need to check whether all of the members of the user's reported symptoms are also a member of one of these symptom-sets. E.g., if we have symptom sets declared like so:

indications_of(herpes, [lesion, inguinal, picazon, fiber]).
indications_of(siphilis, [...]).
indications_of(..., ...).
....

Then the following rule will check for whether all the members of one list are members of another, rather than just trying to unify the lists:

indicate_disease(UserSymptoms, Disease) :-
    indications_of(Disease, DiseaseSymptoms),
    forall(member(Symptom, DiseaseSymptoms), member(Symptom, UserSymptoms)).

Note, this will only work if every one of the DiseaseSymptoms is present in the UserSymptoms. Another approach might be to make sure that all the symptoms are in sorted lists, using sort(List, Sorted) and then test simply by unifying lists.

To find all the diseases which might indicated by a set of symptoms, you might use findall/3 like so:

indicate_diseases(UserSymptoms, Diseases) :-
    findall(Disease,
            ( indications_of(Disease, DiseaseSymptoms),
              forall( member(Symptom, DiseaseSymptoms),
                      member(Symptom, UserSymptoms) ),
            Diseases
            ).

Which we might read to say "the UserSymptoms indicate Diseases, if Diseases is the list of all values for Disease such that DiseaseSymptoms are indications of Disease, and for all elements Symptom of the list of DiseaseSymptoms, Symptom is also an element of the list of UserSymptoms."

Take note that, when using findall/3 a negative result will be an empty list rather than false, because, e.g.,

?- findall(X, X \= X, Xs).
Y = [].

So your report back to the user will have to test for [] rather than failure and the conditional in inquire_about_symptoms/1 will now look more like the following:

...
indicate_diseases(HasSymptoms, Diseases)
( Diseases = []
->  writeln('You are disease free! :)~n'
;   format('You have ~w -- :(~n', [Diseases])
).

I haven't tested any of this added code, but I think the basic ideas are all sound.

OTHER TIPS

Wow, how's that for imagery?

The basic answer to your question is yes. asserta/1 and assertz/1 allow you to add facts to the database at runtime. Classic expert systems one reads about in ancient Prolog texts depended on this functionality. You can add a dynamic declaration if you want to be really precise, in which case the code would probably look something like this:

:- dynamic lesion/1.

has_lesions :- 
  write('Do you have any lesions? '),
  read_term(Ans, []),
  Ans = yes
    -> asserta(lesion(herpes))
     ; true.

This is what using it looks like:

?- has_lesions.
Do you have any lesions? yes.
true.

?- lesion(X).
X = herpes.

You can cleanse the dynamic store like this:

?- retractall(lesion(_)).
true.

?- lesion(X).
false.

Try it again without saying yes..

?- has_lesions.
Do you have any lesions? no.
true.

?- lesion(X).
false.

This is a very rough sketch. Most books on Prolog have a more in-depth discussion of this. You would probably want to start by improving the input routine (users aren't going to want to input Prolog terms), and probably you'd want to make the question asking routines more robust and abstract. Directing the interview so that only useful questions are asked is another thing to think about.

Now I would change a few things about your storage. When you ask someone if they have lesions, neither you nor the user know yet whether the lesions are herpes lesions. So you should probably find a representation of facts that isn't quite as definite. I'd suggest symptom/1 instead, and then your facts would probably look more like:

symptom(lesion).
symptom(headache).

Then your diagnosis predicate could look more like this:

diagnosis(herpes)  :- symptom(lesion), symptom(inguinal), symptom(picazon), ....
diagnosis(herpes)  :- symptom(ampolla), symptom(headache), symptom(picazon).
diagnosis(sifilis) :- symptom(secretion), symptom(lesion).
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top