Рекурсия Prolog пропускает те же результаты

Мой код работает, но проблема в том, что он показывает одни и те же результаты более одного раза. Вот мой код:

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]
    ).

Но мне не удалось применить это к моему коду. Помогите, пожалуйста.


person Mezzan    schedule 29.11.2011    source источник
comment
Возможно, предполагается, что вы проверяете наличие всех симптомов болезни, прежде чем возвращать этот диагноз?   -  person hardmath    schedule 29.11.2011
comment
@hardmath да, это план.   -  person Mezzan    schedule 29.11.2011


Ответы (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.

person false    schedule 29.11.2011
comment
Спасибо @false! С вашим объяснением я начинаю лучше понимать пролог. Извините за загадочность [] вместо имени. Да, я предпочитаю получать диагноз в собственном списке. - person Mezzan; 29.11.2011
comment
Добро пожаловать! При изучении Пролога старайтесь как можно дольше держаться подальше от побочных эффектов встроенных программ. Это чистая часть Пролога, которая делает его таким интересным. - person false; 29.11.2011

В качестве альтернативы вы можете написать:

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).

Но если вы пытаетесь изучить Пролог, это не очень хорошая идея, так как он более функционален и менее прологарен, думаю, я все равно упомяну об этой возможности!

person m09    schedule 29.11.2011
comment
Очень хорошо! Я считаю, что maplist/2 и maplist/3 также очень похожи на пролог — чтобы сделать код более похожим на пролог, небольшое предложение: вместо болезни/2 мы могли бы вызвать предикат, например, заболевание_симптомы/2, чтобы быть более описательным. - person mat; 29.11.2011
comment
@Mog намного лучше выполнять ввод-вывод так, как делаете вы, а не традиционным способом в циклах, вызванных сбоями! Таким образом, ошибки в части ввода-вывода окажутся непреднамеренными сбоями. - person false; 29.11.2011
comment
К мату: я старался не переименовывать факты ОП, но согласен, что Disease_symptoms будет более читабельным! К ложному: спасибо, что указали на это! - person m09; 29.11.2011
comment
@Mog большое спасибо! Ваше решение работает идеально! мне нужно будет понять все findall/3 и maplist/2 и maplist/3, прежде чем я смогу продолжить. - person Mezzan; 29.11.2011
comment
Не стесняйтесь задавать дополнительные вопросы, если вы испытываете затруднения, потому что иногда трудно найти документацию! - person m09; 29.11.2011

Вы должны вспомнить, какие заболевания вы уже собрали при проверке симптомов. Вам необходимо собрать (агрегировать) заболевания в список и проверить, присутствует ли заболевание в списке, прежде чем добавлять его. Затем вы можете распечатать список в конце или распечатать каждое новое заболевание по мере его добавления в список.

Я бы реализовал это так:

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 — это встроенный предикат, поэтому вам не нужно определять свой собственный.

person twinterer    schedule 29.11.2011