跳轉到內容

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].
華夏公益教科書