首先,您提供的代码中有一个错字: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.
这与我认为我可以为您提供所需的解决方案一样接近。我希望它有帮助!