Prolog 递归跳过相同结果

4

我的代码可以运行,但问题是它会重复显示相同的结果。以下是我的代码:

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

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

我该如何避免获得相同的结果?我尝试使用在这里找到的其他方法:
filter_doubles([], []).
filter_doubles([X|L], Result) :-
    (memberchk(X,L) ->
        filter_doubles(L, Result)
    ;
        filter_doubles(L, Result0),
        Result = [X|Result0]
    ).

但是我在我的代码中未能应用它。请求帮助。

你是否打算在返回诊断之前检查疾病的所有症状是否存在? - hardmath
@hardmath 是的,那是计划。 - Mezzan
3个回答

7
你的程序有一个纯净的核心,或者说用医学术语来说,是一个纯净的心脏,但它与癌细胞般的I/O组织交织在一起!因此,如果没有做好,甚至可能做不到。例如,一个小错误就会导致你的程序失败,如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].

现在我们准备好进入普林斯顿-普莱恩斯伯勒教学医院了!只有迷信才会让豪斯医生不接受Prolog的诊断!

关于不纯净的部分,请参考@Mog的方法。


1
谢谢@false!有了你的解释,我开始更好地理解Prolog了。抱歉在应该是Name的地方出现了神秘的[]。是的,我更喜欢将诊断单独列成一个列表。 - Mezzan
2
欢迎!学习Prolog时,尽可能避免使用具有副作用的内置函数。正是Prolog的纯部分使其如此有趣。 - false

3
或者,你也可以写成:
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).

但如果你想学习Prolog,这并不是一个好主意,因为它更加功能化而不是像Prolog一样,虽然我还是想提一下这种可能性!


3
很好!我认为maplist/2和maplist/3也非常符合Prolog的特点。为了使代码更具有Prolog的特点,我的一个小建议是:我们可以将谓词disease/2改为更具描述性的disease_symptoms/2。 - mat
2
@Mog 以你现在的方式执行 I/O 操作比“传统”方式中的错误驱动循环要好得多!这种方式下,I/O 部分的错误会变成意外故障。 - false
给mat:我尽量不重命名OP的事实,但我同意disease_symptoms会更易读!给false:谢谢你指出这一点! - m09
@Mog 非常感谢!你的解决方案完美地解决了我的问题!在继续之前,我需要理解findall/3、maplist/2和maplist/3的所有内容。 - Mezzan
如果你在使用文档时遇到困难,不要犹豫,随时提出更多的问题!有时候找到文档很困难! - m09

2

在检查症状时,您需要记住已经收集到的疾病。您需要在列表中聚合(汇总)疾病,并在添加疾病之前检查该疾病是否已经存在于列表中。然后,您可以在最后打印列表,或在将每个新疾病添加到列表时打印出来。

我会这样实现:

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的第二个子句中插入的一个cut相关:没有cut,您可以获得多个解决方案,每个解决方案代表与症状集相匹配的不同疾病组合。如果添加cut,则仅获得第一个解决方案(包括妊娠)。第二个解决方案只包含流感和艾滋病。
顺便说一下,member/2是内置谓词,因此您无需定义自己的谓词。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接