在Prolog的查詢中,可能存在會有不少解決方案的狀況。好比,假設有以下的知識庫:學習
child(martha, charlotte). child(charlotte, caroline). child(caroline, laura). child(laura, rose). descend(X, Y) :- child(X, Y). descend(X, Y) :- child(X, Z), descend(Z, Y).
若是咱們進行查詢:code
?- descend(martha, X).
將會有四個知足條件的解決方案(分別是:X = charlotte, X = caroline, X = laura, X = rose)。對象
可是Prolog是一個接一個地去生成解決方案的。有些時候咱們但願可以經過一個查詢得出全部解決方案,而且但願以一種乾淨、可用、形式化的結果來顯示。Prolog有三個內置謂詞能夠用來達成這個目標,分別是:findall,bagof,和setof。從本質上來講,全部的這些謂詞都是經過一個查詢收集全部的解決方案,並將其放在一個列表中,可是它們之間存在重要的不一樣之處,咱們將會詳細學習。排序
以下的查詢:io
?- findall(Object, Goal, List).
將會構建一個全部Object知足目標Goal的對象列表。一般來講,Object是一個簡單的變量,這個查詢能夠讀作:請給我一個包含全部Object初始化後可以知足Goal的值的集合。變量
下面是一個例子。假設使用上面的知識庫,若是進行查詢:List
?- findall(X, descend(martha, X), Z).
咱們但願獲得一個列表Z,其中包含了全部X可以知足descend(martha, X)的值,Prolog會回答:grid
X = _7489 Z = [charlotte, caroline, laura, rose]
可是Object不是必須爲一個變量,它也多是一個包含了變量的複雜語句,其中的變量也會出如今Goal中。好比,咱們但願構建一個新的謂詞fromMartha/1,當其中的參數爲Martha的後輩時爲真,那麼能夠進行以下的查詢:語法
?- findall(fromMartha(X), descend(martha, X), Z).
即,但願可以獲得一個列表Z,其中包含了全部fromMartha(X)可以知足descend(martha, X)的值,Prolog會回答:查詢
X = _7616 Z = [fromMartha(charlotte), fromMartha(caroline), fromMartha(laura), fromMartha(rose)] true
若是進行下面的查詢,會發生什麼?
?- findall(X, descend(mary, X), Z).
由於知識庫中沒有知足條件的解決方案,因此findall/3會返回一個空列表。
也有可能存在這種狀況,findall/3前兩個參數不是同一個變量,可是findall/3也能起到做用。好比,若是你對具體是那些人是Martha的後輩不感興趣,而只是關心Martha有多少個後輩,可使用下面的查詢:
?- findall(Y, descend(martha, X), Z), length(Z, N).
謂詞findall/3頗有用,可是在有些方面顯得比較粗糙。好比,進行下面的查詢:
?- findall(Child, descend(Mother, Child), List).
會獲得以下的答案:
Child = _6947 Mother = _6951 List = [charlotte, caroline, laura, rose, caroline, laura, rose, laura, rose, rose]
這個答案是正確的,可是有時候,咱們但願根據不一樣的Mother,有不一樣的分組結果顯示,這樣可讀性會更好。bagof/3能夠達到這個目的,若是進行查詢:
?- bagof(Child, descend(Mother, Child), List).
將會獲得以下的結果:
Child = _7736 Mother = caroline List = [laura, rose]; Child = _7736 Mother = charlotte List = [caroline, laura, rose]; Child = _7736 Mother = laura List = [rose]; Child = _7736 Mother = martha List = [charlotte, caroline, laura, rose]; false
能夠看出,bagof/3比findall/3可讀性更好,它可以獲取出咱們指望的更具結構性的解決方案。並且,bagof/3能夠完成findall/3相同的工做,藉助一個特殊的語法,名爲^:
?- bagof(Child, Mother^descend(Mother, Child), List).
上面查詢的含義是:給出一個列表,其中的元素都是Child的值,而且知足descend(Mother, Child),同時不須要爲每一個Mother生成特殊的結果列表,因此Prolog的回答是:
Child = _7870 Mother = _7874 List = [charlotte, caroline, laura, rose, caroline, laura, rose, laura, rose, rose]
請注意這裏的結果和使用findall/3的徹底一致。因此,若是你但願使用簡單方式獲取全部的解決方案,建議仍是使用findall/3,由於不須要顯式地使用^進行拼接。
findall/3和bagof/3之間有一個重要的區別,就是若是bagof的第二個參數的目標沒法知足時,查詢會致使失敗(findall/3這種狀況會返回空列表),因此查詢 bagof(X, descend(mary, X), Z)會返回false。
最後須要注意的是,思考下面的查詢:
?- bagof(Child, descend(Mother, Child), List).
正如咱們上面看到的,這裏存在四個解決方案。可是,Prolog是一個接一個去生成這些結果的。有沒有一種更好的方式將這些解決方案收集到一個列表中?確實能夠作到,最簡單的方式是使用findall/3,進行查詢:
?- findall(List, bagof(Child, descend(Mother, Child), List), Z).
將bagof/3的解決方案收集到一個列表中:
List = _8293 Child = _8297 Mother = _8301 Z = [[laura, rose], [caroline, laura, rose], [rose], [charlotte, caroline, laura, rose]]
另一種方式是使用bagof/3:
?- bagof(List, Child^Mother^bagof(Child, descend(Mother, Child), List), Z). List = _2648 Child = _2652 Mother = _2655 Z = [[laura, rose], [caroline, laura, rose], [rose], [charlotte, caroline, laura, rose]]
以上例子可能不會常用到,可是它們演示了這些謂詞可以提供的靈活性和強大功能。
謂詞setof/3和bagof/3很相似,可是存在一個有用的區別:setof/3得出的結果列表中的元素是有順序,而且沒有冗餘的(即,列表中不包含重複的元素)。
好比,假設存在下面的知識庫:
age(harry, 13). age(draco, 14). age(ron, 13). age(hermione, 13). age(dumbledore, 60). age(hagrid, 30).
如今假設咱們但願獲得一個包含全部在知識庫中有描述年齡的人的列表,能夠進行以下查詢:
?- findall(X, age(X, Y), Out). X = _8443 Y = _8448 Out = [harry, draco, ron, hermione, dumbledore, hagrid]
可是咱們但願可以得出排序後的列表,可使用以下的查詢:
?- setof(X, Y^age(X, Y), Out).
注意,和bagof/3相似,若是咱們不但願setof/3爲每一個Y單獨生成X的列表,那麼使用^進行過濾,獲得的答案是:
X = _8711 Y = _8715 Out = [draco, dumbledore, hagrid, harry, hermione, ron]
注意上面的列表是根據字母排序的。
如今假設咱們但願可以收集知識庫中全部出現的年齡,能夠進行以下的查詢:
?- findall(Y, age(X, Y), Out). X = _8847 Y = _8851 Out = [13, 14, 13, 13, 60, 30]
可是這個結果比較粗糙,沒有排序,也包含了重複元素。經過使用setof/3,咱們能夠獲得更加整潔的列表:
?- setof(Y, X^age(X, Y), Out). X = _8981 Y = _8985 Out = [13, 14, 30, 60]
以上三個謂詞爲咱們收集查詢的結果提供了很強的靈活性。對於大多數的狀況,findall/3能夠搞定,可是若是須要更加精細的輸出結果,能夠結合bagof/3和setof/3來達成。可是請記住findall/3和兩位兩個謂詞之間的重要不一樣:在沒有符合條件的狀況下,findall/3會返回空列表,其他兩個謂詞則會返回錯誤。