1

学习一些基本的序言,我很难理解逻辑。

情景:一个人感染了病毒性脑膜炎,并且碰巧与其他人发生了互动。到目前为止,这是我的序言逻辑。

%-- Set a sickness condition.
%-- -------------------------------------------------------- --%
setILL(X) :- write(X), write(' is ill'), nl.

%-- Link some interactions between individuals.
%-- -------------------------------------------------------- --%
interact(ella, james).
interact(ella, tyrone).
interact(james, ben).
interact(james, frank).
interact(james, carl).
interact(carl, james).
interact(carl, evan).
interact(evan, mike).
interact(evan, kelly).
interact(mike, frank).
interact(kelly, carl).
interact(kelly, frank).
interact(kelly, ben).
interact(sven, mike).

%-- Create an interaction condition.
%-- -------------------------------------------------------- --%
came_in_contact(X, Y) :- setILL(X), write(X), write(' has had contact with '), write(Y), X\=Y, !, nl.  

%-- Create a rule for sickness
%-- -------------------------------------------------------- --%
sick(X) :- interact(X, Y), contact(X, Y), Y\=X.  
whosick(R) :- findall([X], sick(X), R).

现在交互是它们应该是的,应该有两条路径,每条路径都以 ella (最初生病的人)开始,以 sven (假设的最后一个生病的人)结束。我只是希望打印出两种可能的路径,而不包括无用的交互。例如,tyrone 不会与其他人交谈,ben 也不会。我也希望删除重复(见下文)。

当我执行

whosick(X).

我明白了

ella is ill
ella has had contact with james
ella is ill
ella has had contact with tyrone
james is ill
james has had contact with ben
james is ill
james has had contact with frank
james is ill
james has had contact with carl
carl is ill
carl has had contact with james
carl is ill
carl has had contact with evan
evan is ill
evan has had contact with mike
evan is ill
evan has had contact with kelly
mike is ill
mike has had contact with frank
kelly is ill
kelly has had contact with carl
kelly is ill
kelly has had contact with frank
kelly is ill
kelly has had contact with ben
sven is ill
sven has had contact with mike
X = [[ella], [ella], [james], [james], [james], [carl], [carl], [evan], [...]|...].
4

1 回答 1

2

首先,您提供的代码中有一个错字:came_in_contact应该contact或者它不会运行。小问题。

第二个问题:我不确定你的意思是什么:findall([X], sick(X), R).没有特别的理由在[X]这里使用而不是 just X,并且这个更改的结果看起来更好一点:

X = [ella, ella, james, james, james, carl, carl|...].

一个更重要的问题是,从风格上讲,setill尽管名称听起来很有状态,但它是 100% 的副作用。setill不会“设置”任何人“生病”,它只是打印到标准输出有人生病了。如果这是 MVC,您可能会说,它是“视图”的一部分。因此,您遇到的部分问题是您从sick/2.

您在括号中提到 Ella 是爆发的起源,但是您的 Prolog 数据库中没有任何事实,因此 Prolog 肯定没有意识到这一点。此外,您似乎对感染所采用的“路径”感兴趣,但您的 Prolog 对路径一无所知 - 事实上,它实际上只是在转储您的事实数据库。为了证明这一点,让我们在顶部添加一个新事实:

interact(gail, hank).

果然,它现在是第一个“解决方案”,即使 Gail 和 Hank 与图表的其余部分隔离:

gail is ill
gail has had contact with hank
… (old output repeated)

...

所以,你在这里的杂草丛生。你有一个不完整的事实数据库,你的规则并没有真正捕捉到问题的逻辑,它们在打印中散布了逻辑。当我们在这里完成时,代码看起来会大不相同。我不确定这是否是家庭作业,听起来你是在自学,但它有一种家庭作业的氛围,所以我将尝试勾勒出我将如何在不把它们放在一起的情况下进行。


首先,您需要让 Prolog 了解计算解决方案所需的所有事实。也就是说,您必须添加有关发起者的事实:

infected(ella) :- !.

这将成为基本情况。现在我们需要使用归纳推理并说,如果该人与感染者接触过,则该人被感染:

infected(X) :- interact(X, Y), X \= Y, infected(Y), !.

注意:这些削减是相当重要的。没有必要计算另一个解决方案,因为一个人要么被感染,要么没有被感染。如果我们在任何一个分支上都成功证明他们被感染了,那就没什么好说的了。

现在我们可以为一些人得到合理的解决方案:

?- infected(ella).
true.

?- infected(gail).
false.

其他人似乎没有解决方案:

?- infected(james).
(I typed Ctrl+C)
^CAction (h for help) ? abort
% Execution Aborted

James 没有找到解决方案的原因是 Prolog 使用的是深度优先搜索。方便的是,接下来你要做的就是发现感染路径,所以如果你能阻止 Prolog 尝试已经在路径中的人,你可以通过获取你也需要的路径来解决问题。您将不得不采用类似的基本案例/归纳案例结构,但传递一个额外的感染路径参数。您可以在各处找到此类事情的示例,因此我不会在这里详细介绍您。

请注意这一点:我们不会将问题的逻辑与结果的显示混为一谈。由于回溯,这只是 Prolog 的好策略。如果您因为此处的绑定成功而将某些内容打印出来,而在下一个术语中它失败了,那么整个失败可能会回到打印输出之后,从而使用户感到困惑。我们可以很容易地欺骗 Prolog 从后来失败的解决方案中打印出谎言。所以你总是想编写你的 Prolog 以便它找到解决方案,然后单独显示它们。想想模型-视图-控制器。

所以让我们假设你找到了一个谓词,path/3(大概path(Source, Last, Path))。当你运行它时,你会得到这样的解决方案:

?- path(ella, X, Path).
X = sven
Path = [ella, james, ...] ;

X = sven
Path = [ella, tyrone, ...] ;
false.

这是您要使用 包装的谓词findall/3,然后您将要遍历结果并逐个打印出您需要的部分。

编辑:针对您的评论,让我们来看看您的新谓词:

path(_, X, P) :- findall(X, interact(_, X), P).

恐怕这不会比以前更近了。让我们看看当我向自己询问路径时会发生什么:

?- path('Daniel Lyons', X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].

事实上,你可以把任何东西放在那里,你会得到完全相同的结果:

?- path('Jack Donaghy', X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
?- path(3.1415926, X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
?- path([a,b,c,d,e], X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].

这是因为您的规则适用于任何处于首位的事物。如果你有更多的子句,这可能是有意义的,因为其他子句之一可以说明这个论点,但缺乏它确实意味着什么。所以你的谓词也可以写成:

path(X, P) :- findall(X, interact(_, X), P).

每一个_都是一个完全独特的绑定;它们根本不会相互影响,所以如果你希望在那里产生效果,你会想要更像这样的东西:

path(F, X, P) :- findall(X, interact(F, X), P).

你马上就会发现这对你没有多大帮助:

?- path(ella, X, P).
P = [james, tyrone].

所以我们已经解决了这个问题。

person(X) :- interact(X, _) ; interact(_, X).

这只是一个帮助器,它返回每个人,无论他们是在交互的左侧还是右侧。

path(Originator, Path) :- 
  setof(X, person(X), People), 
  path(Originator, Path, People).

该助手为您提供特定人的路径。我们依赖于一个辅助函数,我将在稍后展示。我们从所有人的名单开始,将可能性修剪成合理的东西。这样我们就可以从我们还没有检查过的人列表中选择下一个人,我们不必担心循环或过多的递归。

path(Originator, [], _).
path(Originator, [NextPerson|Rest], Considering) :-
  select(NextPerson, Considering, RemainingToConsider),
  interact(Originator, NextPerson), 
  path(NextPerson, Rest, RemainingToConsider).

第一个子句说,我们总是可以完成的。从始发者到无人的路径是空路径。这是我们归纳的基本情况。

第二个子句说,从我们剩下要考虑的人列表中选择一个人。有人与发起人互动。现在从那个人到剩下的要考虑的人找到一条路径。(select/3将第三个参数与没有第一个参数的第二个参数统一起来)。

让我们看一下运行:

?- path(ella, X).
X = [] ;
X = [james] ;
X = [james, ben] ;
X = [james, carl] ;
X = [james, carl, evan] ;
X = [james, carl, evan, kelly] ;
X = [james, carl, evan, kelly, ben] ;
X = [james, carl, evan, kelly, frank] ;
X = [james, carl, evan, mike] ;
X = [james, carl, evan, mike, frank] ;
X = [james, frank] ;
X = [tyrone] ;
false.

现在,在您最初的问题中,您说了一些关于本和弗兰克的事情,并且对其他路径不感兴趣。我仍然没有看到可以区分这些情况的逻辑阅读,但您至少可以找到所有最长的路径,如下所示:

longest_paths(Originator, Path) :-
  path(Originator, Path),
  \+ (path(Originator, Path2), 
      length(Path, MaxLen), 
      length(Path2, NextLen), 
      NextLen > MaxLen).

这不是非常有效,但它说的是,从 Originator 为我找到一条路径 Path,这样就没有其他长度更长的路径了。这为我们找到了三个解决方案:

?- longest_paths(ella, X).
X = [james, carl, evan, kelly, ben] ;
X = [james, carl, evan, kelly, frank] ;
X = [james, carl, evan, mike, frank] ;
false.

这与我认为我可以为您提供所需的解决方案一样接近。我希望它有帮助!

于 2013-02-03T07:21:48.873 回答