所以我试图获得树中两个节点之间的最短路径。 我有谓词{{1}},它将Path统一为从开始到结束的所有可能路由。 因为我想要最短的路线,我想得到最短的路径。 我如何“循环”通过Path可以获得的所有选项? 谢谢
答案 0 :(得分:4)
更多"经典"答案是使用一个语外谓词setof/3
,bagof/3
和findall/3
。特别是setof/3
可以通过一个巧妙的技巧重新调整以计算最小值,但是setof/3
和bagof/3
可以在一定程度上反直觉地返回,这取决于内部变量是否存在量化。 / p>
假设我们正在使用经典的圣经亲子数据库。我们可以:
father(abraham, isaac).
father(isaac, jacob).
father(jacob, joseph).
father(jacob, benjamin).
等等。假设你想要父亲的名单。这是一种方式:
?- findall(Father, father(Father, _), Fathers).
Fathers = [abraham, isaac, jacob, jacob].
但是你注意到jacob
在那里两次。你知道,我知道,我会使用setof/3
。然后你明白了:
?- setof(Father, father(Father, _), Fathers).
Fathers = [jacob] ;
Fathers = [abraham] ;
Fathers = [isaac] ;
Fathers = [jacob].
这里发生的事情是Prolog正在统一_
一些值,而这个值限制了结果。换句话说,jacob
出现两次,因为他有两个孩子,每个孩子都是_
的单独绑定。解决方案是使用一些特殊语法指定存在量化:
?- setof(Father, Child^father(Father, Child), Fathers).
Fathers = [abraham, isaac, jacob].
Child^
语法有一种方法可以告诉Prolog:有一些值可以绑定这个变量,但我不在乎它是什么,我不希望我的结果受到约束它。这样我们就可以得到预期的结果。
现在我们可以在一个地方获得路径(开始,结束,路径)的所有解决方案:
?- findall(Path, path(Start, End, Path), Paths).
如果您在阅读时遇到问题,我们所说的是"在表达式路径中找到所有路径绑定(开始,结束,路径),并将它们收集到一个新的变量路径。"
从这里开始,我们可以像对待任何其他列表一样对待它,并通过手动或两者或其他方式对列表进行排序或扫描来找到最小值。
我之前提到过,我们有时会欺骗setof/3
进行最小化。如果您确信path/3
会在合理的时间内终止 - 换句话说,应用于findof/3
的{{1}}不会产生无限循环 - 我们可以执行以下操作特技:
path/3
让我再过一秒钟。早些时候,当我提到setof((Len,Path), (path(Start, End, Path), length(Path, Len)), [(_,ShortestPath)|_]).
的第一个参数是要查找的变量时,它实际上是与第二个参数的每个成功结果统一的表达式。所以我们说要在表达式(Len,Path)中收集Len和Path变量。 注意:这是不是一个元组!这只是使用setof/3
运算符的表达式构建。我更倾向于这样写:
,/2
使用Len-Path或(Len,Path)或任何其他语法来组合Len和Path之间没有区别。重要的是,他们在一些表达中 - 任何表达 - 在一起。 Prolog不会自动与setof(Len-Path, (path(Start, End, Path), length(Path, Len)), [_-ShortestPath|_]).
进行算术运算,并且它不会自动赢得#34;和#34;与-/2
一起的事情,所以我们可以自由地做到这一点。另一个重要的细节是我们需要先考虑长度,因为,/2
将要排序的是什么。
第三个论点看起来非常糟糕,所以让我们分解一下:
setof/3
这只是嵌套在模式中的模式。我们知道至少会有一个结果,所以我们在这里使用列表模式[(_, ShortestPath)|_] % or: [_-ShortestPath|_]
。只是我们不关心尾巴,所以我们有[Head|Tail]
,所以我们要拆分列表的第一个元素。然后我们知道这个列表中的每个项目都将具有第一个子句中的结构[...|_]
。所以我们在这里做的就是期望得到一个看起来像的结果:
(Len, Path)
我们只想从第一个元素中获取[(3, <path>), (4, ...), (5, ...), ...]
。
请注意,我们不必处理其他案件,因为如果没有解决方案,我们无论如何都应该失败!
现在所有这一切,如果你有权访问像聚合这样的库来完成这项工作,那么一定要使用它。我主要认为了解一个人的选择是有益的。 (这个<path>
技巧来自Covington等人的书“ Prolog Programming in Depth ”。
修改:直接行走解决方案列表
Prolog的一个好处 - 甚至可能是 好东西 - 是回溯它会产生所有的解决方案。这使得编写最小化查询非常简单,例如:
seto/3
在逻辑上陈述的是:最年轻的人是有年龄的人年龄,以致没有其他年龄小于年龄的人。这是解决问题的一种非常可爱的方式;问题是它必须遍历每个事实的整个数据库,直到找到解决方案。它的效率很低。
为了更有效地做到这一点,我们必须让自己更加明确我们想做什么。现在,下一个最明确且可能最清晰的解决方案是使用age(abraham, 103). age(isaac, 45).
age(jacob, 88). age(joseph, 46).
youngest(Person) :-
age(Person, Age),
\+ (age(_, Younger), Younger < Age).
生成所有可能性,然后通过递归处理列表来选择正确的可能性。代码可能如下所示:
findall/3
第一条规则是简单地将事实数据库转换为对的列表,第二条规则是以预期的方式处理该列表,方法是携带当前最小值并将每个最小值与当前最小值进行比较以确定它是否替换是不是。取决于你正在做什么,这可能更有效或更少,但这是一个更大的话题。
注意:youngest(Youngest) :-
findall((Person, Age), age(Person, Age), [(FirstPerson,FirstAge)|People]),
youngest_loop(FirstPerson, FirstAge, People, Youngest).
youngest_loop(Youngest, _, [], Youngest).
youngest_loop(CurrentPerson, CurrentMinimumAge, [(NextPerson,NextAge)|People], Youngest) :-
( (CurrentMinimumAge > NextAge) ->
youngest_loop(NextPerson, NextAge, People, Youngest)
; youngest_loop(CurrentPerson, CurrentMinimumAge, People, Youngest)).
,setof/3
和bagof/3
是标准的Prolog,应该随处可用。这不是一个内置的库例程。
现在,关于中间可调用目标的奇怪行为。这里实际上没有任何魔力,它只是经常统一。你可以通过编写一个类似的谓词来证明这一点,比如一个用循环之类的数字重复调用目标的谓词:
findall/3
通过将Var和目标结合在一起,我们能够使用loopy(Start, Stop, Var, Goal) :-
numlist(Start, Stop, Numbers),
member(Var, Numbers),
Goal.
?- loopy(1, 10, X, (write('Generating '), write(X), nl)).
Generating 1
X = 1 ;
Generating 2
X = 2 ;
...etc...
建立Var的绑定,然后让Prolog知道&#34;证明&#34;目标。目标可能是任何Prolog表达式,所以括号表达式在那里&#34;只是工作&#34;。非常重要的是Var匹配Goal中使用的变量,或者你得到这种看起来很奇怪的行为:
member/2
希望这能回答你的问题!
答案 1 :(得分:2)
您可以使用库(aggregate)。
shorter_path(Start, End, MinPath) :-
aggregate(min(Len, Path),
(path(Start, End, Path), length(Path, Len)),
min(Len, MinPath)).