跳转到内容

Prolog/Bagof、Setof 和 Findall

来自维基教科书,开放的书籍,为一个开放的世界

Bagof、Setof 和 Findall 被称为元谓词,因为它们以 ":Goal" 作为参数。元谓词相当于来自函数式编程语言的高阶函数。

什么是 :Goal?

[编辑 | 编辑源代码]

:Goal 是你可以输入到顶层解释器中的任何东西。例如,最简单的元谓词是 call/1。

Call/1 类似于其他脚本语言中的 "eval"。

我们在头注释中用 ":" 前缀标记目标。这只是一个我们在文档和注释中使用的约定。Prolog 解释器对这个约定一无所知。

call/1 的头注释是

call(:Goal)

示例

 ?- call(write('hello')).
 hello
 true.

 ?- call(A=1).
 A = 1.

 ?- X=write('hello'), call(X).
 hello
 X = write(hello).

来自 SICSTUS 手册

 call(:Term) [ISO]
 :Term
 If Term is instantiated to a term which would be acceptable as the body of a clause, then the goal call(Term) is  executed 
 exactly as if that term appeared textually in its place, except that any cut (!) occurring in Term only cuts alternatives
 in the execution of Term.

所有元谓词都以某种方式调用 "call/1"。

在不一直使用 ";" 的情况下找到所有解决方案

[编辑 | 编辑源代码]

有时我们想将标准 Prolog 回溯限制在一个代码块中,并将回溯找到的所有解决方案放入一个列表中,以便我们可以在该块之外使用它们。这时我们使用 "findall/3"。

示例:(SWI prolog)

我们需要模除运算来实现这个示例。"mod/2" 的工作方式如下

 ?- A is mod(5,2).
 A = 1.

 ?- A is mod(4,2).
 A = 0.

我们还需要 numlist/3,它只是生成一个连续整数的列表。

 ?- numlist(1,8,X).
 X = [1, 2, 3, 4, 5, 6, 7, 8].

现在是重点:我们想要从上面的列表中过滤出奇数。为此我们使用 findall/3

findall(+Template, :Goal, -Bag)
?- findall(X, (numlist(1,8,NL),member(X,NL),0 =:= mod(X,2)) ,L).
L = [2, 4, 6, 8].

如您所见,中间参数是一个简单的目标。如果复制并将其输入到顶层解释器中,您将得到以下结果

 ?- numlist(1,8,NL),member(X,NL),0 =:= mod(X,2).
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 X = 2 ;
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 X = 4 ;
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 X = 6 ;
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 X = 8 ;
 false.

findall/3 的第一个参数表示我们要在第三个参数的列表中收集的变量。

您可以将上面的示例理解为:找到所有 X,其中 X 满足 :Goal。

Bagof 与此非常相似,但您也可以在 +Template 中使用存在量词 "^"。存在量词应用的变量不会被收集到结果列表中。

Setof 类似于 bagof,但结果列表是有序的并且不包含重复项。

更多示例

[编辑 | 编辑源代码]

查找除数对

 ?- findall(X-Y, (numlist(1,8,NL),member(X,NL),member(Y,NL),X>Y,Y =\=1, 0 =:= mod(X,Y)), L).
 L = [4-2, 6-2, 6-3, 8-2, 8-4].

相同,但我们不关心 Y。

 ?- bagof(X, Y^(numlist(1,8,NL),member(X,NL),member(Y,NL),X>Y,Y =\=1, 0 =:= mod(X,Y)), L).
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 L = [4, 6, 6, 8, 8].

过滤掉重复项

 ?- setof(X, Y^(numlist(1,8,NL),member(X,NL),member(Y,NL),X>Y,Y =\=1, 0 =:= mod(X,Y)), L).
 NL = [1, 2, 3, 4, 5, 6, 7, 8],
 L = [4, 6, 8].
华夏公益教科书