问题
My code runs but the problem is it shows the same results more than once. Here's my code:
disease(hiv,[sore_throat,headache,fever,rash]).
disease(pregnancy,[fatigue,vomiting,light_headedness,increased_waistline]).
disease(flu,[fatigue,fever,tiredness,nasal_discharge]).
diagnose([], []).
diagnose(Name, [H|T]) :-
disease(The_Disease, Symptoms),
member(H, Symptoms),
write(Name), write(' has/is '), writeln(The_Disease),
diagnose(Name, T).
member(X,[X|_]).
member(X,[_|T]):-
member(X,T).
Result when executed in prolog:
?- diagnose(kevin,[sore_throat,fatigue,tiredness,rash]).
kevin has/is hiv
kevin has/is pregnancy
kevin has/is flu
kevin has/is hiv
kevin has/is flu
kevin has/is flu
kevin has/is hiv
false.
How do I avoid that same results? I tried using other method I found here:
filter_doubles([], []).
filter_doubles([X|L], Result) :-
(memberchk(X,L) ->
filter_doubles(L, Result)
;
filter_doubles(L, Result0),
Result = [X|Result0]
).
But I failed to apply it to my code. Help please.
回答1:
Your program has a pure core - or to stick to medical terms - a pure heart, but this is intertwined with cancerous I/O tissue! In this manner doing it right is very difficult, if not impossible. For example, as a minor error, your program fails for kevin
. But you probably meant it to succeed. On the other hand, you will succeed for the mysterious mister []
! Who is that?
So lets separate the pure from the impure!
The pure part in your program is about relating a list of symptoms to possible diagnoses. Your working assumption is that if there is one symptom which is part of the indications for a malady, we will diagnose that malady - just to be sure. So why not call this symptoms_diagnosis/2
?
symptoms_diagnosis(Symptoms, Diagnosis) :-
member(Symptom, Symptoms),
disease(Diagnosis, Indications),
member(Symptom, Indications).
?- symptoms_diagnosis([sore_throat,fatigue,tiredness,rash], Diagnosis).
Diagnosis = hiv ;
Diagnosis = pregnancy ;
Diagnosis = flu ;
Diagnosis = flu ;
Diagnosis = hiv ;
false.
Note that even without any further ado, we have less redundant solutions than in your original program. So how to get rid of the remaining redundant solutions? This does the trick:
?- setof(t,symptoms_diagnosis([sore_throat,fatigue,tiredness,rash], Diagnosis),_).
Diagnosis = flu ;
Diagnosis = hiv ;
Diagnosis = pregnancy.
So whenever you get redundant solutions, simply wrap a setof(t, ..., _)
around your goal.
You can use that whenever the answers are ground answers. That is, there is no variable left in the answer.
Maybe you prefer to get the diagnosis in a list of its own?
?- setof(Diagnosis,symptoms_diagnosis([sore_throat,fatigue,tiredness,rash],Diagnosis),Diagnoses).
Diagnoses = [flu, hiv, pregnancy].
So, now we are ready for the Princeton-Plainsboro Teaching Hospital! It is only superstition if Dr. House will not accept Prolog's diagnosis!
For the impure part, please look at @Mog's approach.
回答2:
Alternatively, you can write :
disease(hiv,[sore_throat,headache,fever,rash]).
disease(pregnancy,[fatigue,vomiting,light_headedness,increased_waistline]).
disease(flu,[fatigue,fever,tiredness,nasal_discharge]).
diagnose(Name, Symptoms) :-
findall(D, (disease(D, S), intersection(S, Symptoms, I), I \== []), MayGot),
atomic_concat(Name, ' has/is ', Start),
maplist(atomic_concat(Start), MayGot, Temp),
maplist(writeln, Temp).
But if you're trying to learn Prolog it's not a good idea since it's more functionnal and less Prolog-ish, thought I'd mention this possibility anyway !
回答3:
You have to remember what diseases you already collected when checking the symptoms. You need to collect (aggregate) the diseases in a list, and check whether a disease is already present in the list before adding it. You can then print the list at the end or print out each new disease as it is added to the list.
I would implement it this way:
diagnose(Name, Symptoms) :- diagnose0(Name, Symptoms, []).
diagnose0(Name, [], Diseases) :-
print_diseases(Name, Diseases).
diagnose0(Name, [H|T], DIn) :-
disease(Disease, Symptoms),
member(H, Symptoms),
% you may want to add a cut here to avoid choicepoints
(
member(Disease, DIn)
->
diagnose0(Name, T, DIn)
;
DOut = [Disease|DIn],
diagnose0(Name, T, DOut)
).
print_diseases(_Name, []).
print_diseases(Name, [H|T]) :-
write(Name), write(' has/is '), writeln(H),
print_diseases(Name, T).
The disease/2
facts are as in your code.
This gives:
?- diagnose(kevin, [sore_throat, fatigue, tiredness, rash]).
kevin has/is flu
kevin has/is pregnancy
kevin has/is hiv
Yes (0.00s cpu, solution 1, maybe more)
The next step then would obviously be to find a way to express that some diagnoses represent alternatives for a given symptom, and choose between these different alternatives. With the symptoms listed in the query, Kevin should have flu and HIV, but I doubt that pregnancy is a correct diagnosis for Kevin. This is related to the comment about a cut that I inserted in the second clause of diagnose/3
: without the cut, you can get more than one solution, each representing a different set of diseases that match the set of symptoms. If you add a cut, you only get the first solution (including pregnancy). The second solution only contains flu and HIV.
BTW, member/2
is a built-in predicate, so you don't need to define your own.
来源:https://stackoverflow.com/questions/8305811/prolog-recursion-skipping-same-results