前几天我在Prolog中提出了一个关于基本输入/输出的问题,但现在我还有另外一个问题。
我正在我的大学做一个与ITS诊断相关的Prolog项目。
我们说我从一些基本事实开始:
lesion(herpes).
lesion(sifilis).
ampolla(herpes).
inguinal(herpes).
fiber(herpes).
headache(herpes).
picazon(herpes).
secrecion(sifilis).
我有一些基本的测试规则:
its(herpes):-lesion(herpes), inguinal(herpes), picazon(herpes), fiber(herpes).
its(herpes):- ampolla(herpes), headache(herpes), picazon(herpes).
its(sifilis):-secrecion(sifilis), lesion(sifilis).
我的问题是:
有什么方法可以通过使用问题而不是自己设置来保存每个参数?让我解释一下:
例如,我想声明一个空的事实,如:lesion(_)
,然后问用户:
Do you have any lesion?
如果答案是肯定的,那么事实将是:lesion(herpes).
在那之后,我会问另一个问题,直到得到一个论证的所有事实,所以我可以打印:X =疱疹或类似的东西。
这甚至可能吗?
任何帮助都将不胜感激。
答案 0 :(得分:5)
您问题的基本答案是是。 asserta/1
和assertz/1
允许您在运行时向数据库添加事实。古代Prolog文本中的经典专家系统依赖于此功能。如果您想要非常精确,可以添加dynamic
声明,在这种情况下,代码可能看起来像这样:
:- dynamic lesion/1.
has_lesions :-
write('Do you have any lesions? '),
read_term(Ans, []),
Ans = yes
-> asserta(lesion(herpes))
; true.
这就是使用它的样子:
?- has_lesions.
Do you have any lesions? yes.
true.
?- lesion(X).
X = herpes.
您可以像这样清理动态商店:
?- retractall(lesion(_)).
true.
?- lesion(X).
false.
再试一次,不要说yes.
。
?- has_lesions.
Do you have any lesions? no.
true.
?- lesion(X).
false.
这是一个非常粗略的草图。关于Prolog的大多数书籍都对此进行了更深入的讨论。您可能希望从改进输入例程开始(用户不想输入Prolog术语),并且您可能想要提出的问题要求例程更加健壮和抽象。指导面试以便只提出有用的问题是另一回事。
现在我要改变一些关于你的存储的事情。当你问某人是否有病变时,你和使用者都不知道病变是否是疱疹病变。所以你应该找到一个不太明确的事实表示。我建议改为symptom/1
,然后你的事实看起来可能更像:
symptom(lesion).
symptom(headache).
然后你的诊断谓词可能看起来更像这样:
diagnosis(herpes) :- symptom(lesion), symptom(inguinal), symptom(picazon), ....
diagnosis(herpes) :- symptom(ampolla), symptom(headache), symptom(picazon).
diagnosis(sifilis) :- symptom(secretion), symptom(lesion).
答案 1 :(得分:4)
修改:我已经意识到这并没有回答您提出的问题,而是提出了另一种解决问题的方法。
在Prolog中很容易实现您想要实现的目标,但我认为从头开始重新设计您的方法可能是有意义的。
Prolog语法来自first order predicate calculus。因此,p(s)
形式的单个地点谓词的标准读数是" s
是p
"。仿函数p
被视为谓词,将一些质量或属性归为s
。这种方法的一个相当常见的扩展是读取具有更高arity的谓词 - 即更多参数 - 作为事物之间关系的归因,因此可以读取r(a,b)
" a
代表关系r
到b
"。
根据这个共同阅读,你的事实会说"疱疹是病变,梅毒是病变,疱疹是安瓿等..."这没有多大意义。更重要的是,您当前的代码并没有定义任何关系,因此无法通过统一得出任何有趣的事实。
its/1
的规则表现出同样不同寻常的,也许是误导的方法(并不是说不寻常=误入歧途)。通常,我们将每个谓词视为表达基本陈述。如果我想说明苏格拉底是凡人的事实,我写mortal(socrates)
包括主题和谓词的 。同样,如果我想表达苏格拉底教授柏拉图的关系事实,我会写teaches(socrates, plato)
,其中包括表征关系的relata和functor teaches
。我将its(herpes)
视为"它是疱疹",但它不清楚它是什么'它'是指并且没有关于疱疹的关系或财产类型的任何信息。
我将为您的程序提出另一种方法,之后我会提供一些代码来实现所需的输入/输出接口。
我认为你的事实意味着描述疾病的症状。我会使用谓词symptom/2
来明确这一点,以便symptom(Symptom, 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).
请注意,这组事实已经让我们以事实不具备的方式提取有用的信息。例如,我们可以通过查询Symptom
参数的自由变量来找出疾病的所有症状:
?- symptom(X, herpes).
X = lesion ;
X = ampolla ;
X = inguinal ;
X = fiber ;
X = headache ;
X = picazon.
或者我们可以通过查询Disease
参数中的自由变量来找出特定症状可能指示哪些疾病:
?- symptom(inguinal, X).
X = herpes.
谈到你的规则,我认为他们试图说出一个患有这种症状的人可以被诊断患有这种疾病。我可以这样写:
%% 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) ).
我读了规则说,"列表Symptoms
中的症状表示疾病Disease
,如果,对于每个Symptom
,它是列表的成员{{ 1}},Symptoms
是疾病Symptom
"的症状。当我的代码最终读起来像一个非常繁琐,完全冗余的陈述时,我通常觉得我走在正确的轨道上。
当然,如果您只需要特定的症状表示疾病,那么您需要使规则不那么通用。您可以通过多种方式执行此操作。例如,您可以编写谓词Disease
以包含特定的症状分类:
indicate_disease/2
或者您可以改为编写一个中间谓词来描述足以表明诊断的特定症状 - 疾病关系集:
indicate_disease(Symptoms, herpes) :-
Symptoms = [lesion, inguinal, picazon, fiber].
然后编写indications_of(sifilis, [secrecion, lesion]).
以使用此谓词:
indicate_disease/2
通过这样的代码,我们现在可以"诊断" (我们假装)只是以这种方式查询:
indicate_disease(Symptoms, Disease) :-
indications_of(Disease, Symptoms).
现在您的问题发挥作用:如何收集用户输入的症状?如果您只是使用终端作为界面,则以下模式应该足够了:
?- indicate_disease([lesion, inguinal, picazon, fiber], Disease).
Disease = herpes.
这样的用法:
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).
修改:已添加,以便在评论中解决后续问题。
您可以通过从检查某些症状是否是某种疾病的规则中删除关于哪些症状集指示疾病的事实,以任何顺序将症状与一组适应症相匹配。然后,您只需要检查用户所报告的症状的所有成员是否也是这些症状集之一的成员。例如,如果我们有这样的症状集:
?- 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 -- :(
然后,以下规则将检查一个列表的所有成员是否是另一个列表的成员,而不是仅仅尝试统一列表:
indications_of(herpes, [lesion, inguinal, picazon, fiber]).
indications_of(siphilis, [...]).
indications_of(..., ...).
....
请注意,只有在indicate_disease(UserSymptoms, Disease) :-
indications_of(Disease, DiseaseSymptoms),
forall(member(Symptom, DiseaseSymptoms), member(Symptom, UserSymptoms)).
中出现DiseaseSymptoms
的每一个时,此功能才有效。另一种方法可能是使用UserSymptoms
确保所有症状都在排序列表中,然后通过统一列表进行测试。
要查找可能由一组症状指示的所有疾病,您可以使用findall/3
,如下所示:
sort(List, Sorted)
我们可能会读到" indicate_diseases(UserSymptoms, Diseases) :-
findall(Disease,
( indications_of(Disease, DiseaseSymptoms),
forall( member(Symptom, DiseaseSymptoms),
member(Symptom, UserSymptoms) ),
Diseases
).
表示UserSymptoms
,如果Diseases
是Diseases
的所有值的列表,那么Disease
} DiseaseSymptoms
的指示,Disease
列表的所有元素Symptom
,DiseaseSymptoms
也是Symptom
列表的元素。&# 34;
请注意,使用UserSymptoms
时,否定结果将是空列表而不是findall/3
,因为,例如,
false
因此,向用户提交的报告必须测试?- findall(X, X \= X, Xs).
Y = [].
而非失败,[]
中的条件现在看起来更像以下内容:
inquire_about_symptoms/1
我还没有对这些添加的代码进行测试,但我认为基本的想法都很合理。