在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会返回空列表,其他两个谓词则会返回错误。