Prolog Recursion пропускает те же результаты
Мой код выполняется, но проблема в том, что он показывает одни и те же результаты более одного раза. Вот мой код:
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).
Результат при выполнении в прологе:
?- 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.
Как мне избежать таких же результатов? Я пытался использовать другой метод, который я нашел здесь:
filter_doubles([], []).
filter_doubles([X|L], Result) :-
(memberchk(X,L) ->
filter_doubles(L, Result)
;
filter_doubles(L, Result0),
Result = [X|Result0]
).
Но я не смог применить его к своему коду. Помогите, пожалуйста.
3 ответа
Ваша программа имеет чистое ядро - или придерживаться медицинских терминов - чистое сердце, но оно переплетено с раковой тканью ввода / вывода! Таким образом, сделать это правильно очень сложно, если не невозможно. Например, как небольшая ошибка, ваша программа не работает kevin
, Но вы, вероятно, имели в виду, чтобы добиться успеха. С другой стороны, вам удастся загадочный господин []
! Это кто?
Итак, давайте отделим чистое от нечистого!
Чистая часть вашей программы касается привязки списка симптомов к возможным диагнозам. Ваше рабочее предположение состоит в том, что если есть один симптом, который является частью показаний к заболеванию, мы диагностируем это заболевание - просто чтобы быть уверенным. Так почему бы не назвать это 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.
Обратите внимание, что даже без лишних слов у нас есть меньше избыточных решений, чем в вашей оригинальной программе. Так как же избавиться от оставшихся избыточных решений? Это делает трюк:
?- setof(t,symptoms_diagnosis([sore_throat,fatigue,tiredness,rash], Diagnosis),_).
Diagnosis = flu ;
Diagnosis = hiv ;
Diagnosis = pregnancy.
Поэтому, когда вы получаете избыточные решения, просто оберните setof(t, ..., _)
вокруг вашей цели. Вы можете использовать это всякий раз, когда ответы являются наземными. То есть в ответе не осталось переменной.
Может быть, вы предпочитаете поставить диагноз в своем списке?
?- setof(Diagnosis,symptoms_diagnosis([sore_throat,fatigue,tiredness,rash],Diagnosis),Diagnoses).
Diagnoses = [flu, hiv, pregnancy].
Итак, теперь мы готовы к учебной больнице Принстон-Плейнсборо! Это только суеверие, если доктор Хаус не примет диагноз Пролога!
Для нечистой части, пожалуйста, посмотрите на подход @Mog.
В качестве альтернативы вы можете написать:
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).
Но если вы пытаетесь выучить пролог, это не очень хорошая идея, так как он более функциональный и менее прологичный, подумал, что я все равно упомяну эту возможность!
Вы должны помнить, какие заболевания вы уже собрали при проверке симптомов. Вам необходимо собрать (объединить) заболевания в списке и проверить, присутствует ли заболевание в списке, прежде чем добавлять его. Затем вы можете распечатать список в конце или распечатать каждую новую болезнь по мере ее добавления в список.
Я бы реализовал это так:
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).
disease/2
факты как в вашем коде.
Это дает:
?- 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)
Следующий шаг, очевидно, заключается в том, чтобы найти способ выразить, что некоторые диагнозы представляют альтернативы для данного симптома, и выбрать между этими различными альтернативами. С симптомами, перечисленными в запросе, у Кевина должны быть грипп и ВИЧ, но я сомневаюсь, что беременность - правильный диагноз для Кевина. Это связано с комментарием о разрезе, который я вставил во второй пункт diagnose/3
без среза вы можете получить более одного решения, каждое из которых представляет собой набор заболеваний, соответствующих набору симптомов. Если вы добавите сокращение, вы получите только первое решение (включая беременность). Второе решение содержит только грипп и ВИЧ.
КСТАТИ, member/2
это встроенный предикат, поэтому вам не нужно определять свой собственный.