数据库操作和收集解

转载自:https://mp.weixin.qq.com/s/5tNIRd0XRtuhO-6zcQ3_UA

本章有两个主要目标:

1.讨论Prolog中的数据库操作。

2.讨论内置谓词,这些谓词使我们将问题的所有解答收集到一个表中

 

1   数据库操作

Prolog具有四个数据库操作命令:assert,retract,asserta和assertz。让我们看看如何使用它们。假设我们从一个空的数据库开始。因此,如果我们给出命令:

 

?-listing.

 

然后Prolog只会回答true;列出(当然)是空的。

假设我们现在给出以下命令:

?- assert(happy(mia)).

 

这个成功(assert / 1命令始终成功)。但是重要的不是它是否成功,而是它对数据库的副作用。因为如果我们现在给出命令

 

?- listing.

 

我们得到

 

happy(mia).

 

也就是说,数据库不再为空:它现在包含我们声明的事实。

假设然后再发出四个声明命令:

 

?- assert(happy(vincent)).

true

?- assert(happy(marcellus)).

true

?- assert(happy(butch)).

true

?- assert(happy(vincent)).

true

 

然后要求列出:

 

?-listing.

 

happy(mia).

happy(vincent).

happy(marcellus).

happy(butch).

happy(vincent).

true.

 

我们声明的所有事实现在都在知识库中。请注意,happy(vincent)在知识库中有两次。正如我们两次声明的那样,这似乎是明智的。

我们一直在进行的数据库操作改变了谓词happy / 1的含义。更一般而言,数据库操作命令使我们能够在运行程序时更改谓词的含义。其定义在运行时发生变化的谓词称为动态谓词,与我们之前处理过的静态谓词相反。大多数Prolog解释器坚持认为,我们明确声明希望动态的谓词。我们很快将研究一个涉及动态谓词的示例,但首先让我们完成对数据库操作命令的讨论。

到目前为止,我们仅将事实声明到数据库中,而且我们还可以声明新规则。假设我们要主张一个规则,即每个happy的人都naive。也就是说,假设我们要声明:

 

naive(X): happy(X).

 

我们可以这样做,如下所示:

 

assert((naive(X):- happy(X))).

 

请注意此命令的语法:我们要声明的规则放在一对括号中。如果现在我们要求表,我们得到:

 

happy(mia).

happy(vincent).

happy(marcellus).

happy(butch).

happy(vincent).

 

naive(A):-

happy(A).

 

既然我们知道了如何将新信息声明到数据库中,我们还应该学习如何在不再需要信息时将其删除。 assert / 1有一个相反操作的谓词,即retract / 1。例如,如果我们通过给出以下命令直接继续上一个示例:

 

?- retract(happy(marcellus)).

 

然后列出数据库,我们得到:

 

happy(mia).

happy(vincent).

happy(butch).

happy(vincent).

 

naive(A) :-

happy(A).

 

也就是,事实happy(marcellus)已被删除。

假设我们继续说

 

?- retract(happy(vincent)).

 

然后要求列出。我们得到:

 

happy(mia).

happy(butch).

happy(vincent).

 

naive(A) :-

happy(A).

 

请注意,第一次出现的happy(vincent)并且仅仅是第一次出现的被删除。

要删除所有assert定义的谓词happy / 1,我们可以使用一个变量:

 

?- retract(happy(X)).

 

X = mia ;

 

X = butch ;

 

X = vincent.

 

清单显示该数据库现在为空,但规则naive(A) :- happy(A).除外

 

?- listing.

naive(A):- 

happy(A).

 

如果我们想更好地控制所声明的资料的放置位置,则有assert / 1的两个变体,即:

 

1.assertz将声明的资料放在数据库的末尾。

2.asserta将声明的资料放在数据库的开头。

 

例如,假设我们从一个空的数据库开始,然后给出以下命令:

 

assert( p(b) ), assertz( p(c) ), asserta( p(a) ).

 

然后清单显示我们现在拥有以下数据库:

 

?- listing.

 

p(a).

p(b).

p(c).

true.

 

数据库操作是一种有用的技术。这对于将结果存储到计算中特别有用,这样,将来如果我们需要提出相同的问题,就无需重做工作:我们只需查找已断定的事实。这种技术称为记忆或缓存,在某些应用程序中,它可以大大提高效率。这是使用此技术的简单示例:

 

:- dynamic lookup/3.

 

add_and_square(X,Y,Res):-

lookup(X,Y,Res), !.

 

add_and_square(X,Y,Res):-

Res is (X+Y)*(X+Y),

assert(lookup(X,Y,Res)).

这个程序做什么?基本上,它使用两个数字X和Y,将X加到Y,然后对结果求平方。例如,我们有:

 

?- add_and_square(3,7,X).

 

X = 100.

 

但是重要的一点是:它是如何做到的?首先,请注意,我们已将lookup / 3声明为动态谓词。我们需要这样做,因为我们计划在运行时更改lookup / 3的定义。第二,请注意,有两个子句定义add_and_square / 3。第二个子句执行所需的算术计算,并使用谓词lookup / 3将结果声明到Prolog数据库中(即,它缓存结果)。第一个子句检查Prolog数据库,以查看过去是否已经进行过计算。如果已经存在,则程序仅返回结果,并且截断会阻止其进入第二子句。

 

?- add_and_square(3,4,Y).

 

Y = 49

 

如果现在我们要求一个清单,我们会看到数据库现在包含

 

lookup(3,7,100).

lookup(3,4,49).

 

如果我们以后要求Prolog将3和4相加并平方,它将不再执行计算。相反,它将仅返回先前计算的结果。

问题:当我们不再需要这些新事实时,我们该如何删除它们?毕竟,如果我们给出命令

 

?- retract(lookup(X,Y,Z)).

 

Prolog将一一介绍所有事实,并询问我们是否要删除它们!但是有一种更简单的方法。只需使用命令

 

?-retract(lookup(_,_,_)).

 

这将从数据库中删除所有有关lookup / 3的事实。

在结束我们对数据库操作的讨论时,请注意。尽管这是一种有用的技术,但数据库操作会导致脏的,难以理解的代码。如果您在具有大量回溯功能的程序中大量使用它,那么了解正在发生的事情可能是一场噩梦。这是Prolog的非声明性,非逻辑性的功能,应谨慎使用。

 

2   收集解

一个查询可能有很多解答。例如,假设我们正在使用数据库

 

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).

 

然后,如果我们提出查询

 

descend(martha,Y).

 

有四种解答(即X =charlotte,X =caroline,X =laura和X =rose)。

但是,Prolog会一一生成这些解答。有时,我们希望拥有查询的所有解答,并且希望它们以整洁,可用的形式交给我们。 Prolog具有执行此操作的三个内置谓词:findall,bagof和setof。从本质上讲,所有这些谓词都会收集查询的所有解答并将它们放在一个表中,但是如我们所见,它们之间存在重要区别。

findall/3谓词

查询

 

?- findall(Object,Goal,List).

 

产生满足目标Goal的所有对象Object的表List。通常,Object只是一个变量,在这种情况下,查询可以理解为:给我一个包含满足Goal的所有Object实例化的表。

这是一个例子。假设我们正在使用上面的数据库(即有关child和子孙定义的信息)。然后,如果我们构成查询

 

?- findall(X,descend(martha,X),Z).

 

我们需要一个表Z,其中包含满足descend(martha,X)的X的所有值。 Prolog会回应

 

X = _7489

Z = [charlotte,caroline,laura,rose]

 

但是Object不必一定是变量,它可能是一个复合项,只包含一个也出现在Goal中的变量。例如,我们可能决定要从Martha / 1中建立一个仅对玛莎后裔适用的谓词。我们可以通过查询来做到这一点:

 

?- findall(fromMartha(X),descend(martha,X),Z).

 

也就是说,我们要获取一个表Z,其中包含满足目标descend(martha,X)的fromMartha(X)的所有实例。 Prolog会回应

Z = [fromMartha(charlotte), fromMartha(caroline), fromMartha(laura), fromMartha(rose)].

如果我们询问以下查询会怎样?

 

?- findall(X,descend(mary,X),Z).

 

由于在知识库中没有针对目标descend(mary,X)的解决方案。 findall / 3返回一个空表。

请注意,findall / 3的前两个参数通常具有(至少)一个公共变量。使用findall / 3时,我们通常想知道Prolog为目标中的某些变量找到了什么解答,并且通过将它们构建到findall / 3的第一个参数中来告诉Prolog我们对目标中的哪些变量感兴趣

但是,您可能会遇到这样的情况,尽管前两个参数不共享任何变量,但findall / 3会发挥作用,例如,如果您对究竟是谁是玛莎的后代不感兴趣,而只对玛莎有多少后代感兴趣,您可以使用以下查询来找出:

 

?- findall(Y,descend(martha,X),Z), length(Z,N).

 

bagof / 3谓词

findall / 3谓词很有用,但在某些方面还很粗糙。例如,假设我们构成查询

 

?- findall(Child,descend(Mother,Child),List).

 

我们得到回应

 

List = [charlotte, caroline, laura, rose, caroline, laura, rose, laura, rose|...].

 

现在,这是正确的,但是有时候,如果我们为Mother的每个不同实例都有一个单独的表,这将很有用。

这就是bagof / 3所所做的。如果我们提出查询

 

?- bagof(Child,descend(Mother,Child),List).

 

我们得到了回应

 

Mother = caroline,

List = [laura, rose] ;

Mother = charlotte,

List = [caroline, laura, rose] ;

Mother = laura,

List = [rose] ;

Mother = martha,

List = [charlotte, caroline, laura, rose].

 

也就是说,bagof / 3比的findall / 3更细化。它使我们有机会以更结构化的方式提取所需的信息。此外,在特殊语法(即^)的帮助下,bagof / 3也可以完成与findall / 3相同的工作:

 

?- bagof(Child,Mother^descend(Mother,Child),List).

 

这就是说:给我一个有关Child的所有值的表,例如descend(Mother,Child),并将结果放入表中,但不必担心会为每个Mother值生成单独的表。因此,构成此查询将产生:

 

List = [charlotte, caroline, laura, rose, caroline, laura, rose, laura, rose|...].

 

请注意,这正是findall / 3会给我们的响应。不过,如果您要进行这种查询(通常是这样),则使用findall / 3会更简单,因为这样就不必费心用^写下条件。

findall / 3和bagof / 3之间有一个重要的区别,即,如果不满足在其第二个参数中指定的目标,bagof / 3将失败(请记住,在这种情况下,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的所有回复收集到一个表中:

 

Z = [[laura, rose], [caroline, laura, rose], [rose], [charlotte, caroline, laura, rose]].

 

另一种方法是使用bagof / 3:

 

?- bagof(List,Child^Mother^bagof(Child,descend(Mother,Child),List),Z).

 

Z = [[laura,rose],[caroline,laura,rose],[rose],[charlotte,caroline,laura,rose]]

 

这可能不是您经常需要做的事情,但是它确实显示了这些谓词所提供的灵活性和强大功能。

 

setof / 3谓词

setof / 3谓词与bagof / 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).

Out = [harry,draco,ron,hermione,dumbledore,hagrid].

 

但也许我们希望得到一个有序的表。我们可以通过以下查询来实现:

 

?- setof(X,Y^age(X,Y),Out).

 

(请注意,就像bagof / 3一样,我们必须告诉setof / 3不要为每个Y值生成单独的表,并且再次使用^符号来完成此操作。)此查询产生:

 

Out = [draco,dumbledore,hagrid,harry,hermione,ron]

 

请注意,该表按字母顺序排列。

现在假设我们有兴趣收集数据库中记录的所有年龄。当然,我们可以使用以下查询执行此操作:

 

?- findall(Y,age(X,Y),Out).

Out = [13,14,13,13,60,30]

 

但是,此输出相当混乱。它是无序的并且包含重复项。通过使用setof / 3,我们可以以更整洁的形式获得相同的信息:

 

?- setof(Y,X^age(X,Y),Out).

Out = [13,14,30,60]

 

在收集解答时,这三个谓词为我们提供了极大的灵活性。对于许多目的,我们需要的只是findall / 3,但是如果需要更多,bagof / 3和setof / 3都在待用状态。但是请记住,一方面findall / 3与另一方面bagof / 3和setof / 3之间存在重要区别:如果目标没有解答,findall / 3将返回一个空表,而bagof / 3和setof / 3在这种情况下,将失败。

 

3   练习

练习11.1   假设我们从一个空的数据库开始。然后我们给出命令:

 

assert(q(a,b)), assertz(q(1,2)), asserta(q(foo,blug)).

 

数据库现在包含什么?

We then give the command:

 

retract(q(1,2)), assertz( (p(X) :- h(X)) ).

 

数据库现在包含什么?

 

练习11.2   假设我们有以下数据库:

 

q(blob,blug).

q(blob,blag).

q(blob,blig).

q(blaf,blag).

q(dang,dong).

q(dang,blug).

q(flab,blob).

 

Prolog对查询的答复是什么:

findall(X,q(blob,X),List).

findall(X,q(X,blug),List).

findall(X,q(X,Y),List).

bagof(X,q(X,Y),List).

setof(X,Y^q(X,Y),List).

 

练习11.3   编写一个谓词sigma / 2,它采用整数n> 0并计算从1到n的所有整数之和。例如:

 

?- sigma(3,X).

X = 6.

?- sigma(5,X).

X = 15.

 

编写谓词,以便将结果存储在数据库中(每个值在数据库中永远不应有多个条目),并尽可能重用。例如,假设我们进行以下查询:

 

?- sigma(2,X).

X = 3.

 

?- listing(sigmares/2).

:- dynamic sigmares/2.

 

sigmares(0, 0).

sigmares(1, 1).

sigmares(2, 3).

 

true.

 

那我们继续一下问

 

?- sigma(3,X).

 

Prolog不应计算所有新数据,而应从数据库中获取sigma(2,3)的结果,并仅将其加3。然后应该回答:

 

X = 6.

 

?- listing(sigmares/2).

:- dynamic sigmares/2.

 

sigmares(0, 0).

sigmares(1, 1).

sigmares(2, 3).

sigmares(3, 6).

 

true.

 

4   实践环节

请尝试以下两个编程练习:

 

1.集合可被视为不包含任何重复元素的表。例如,[a,4,6]是一个集合,但[a,4,6,a]不是一个集合(因为它包含两次出现的a)。编写一个Prolog程序subset / 2,当第一个自变量是第二个自变量的子集时(即,第一个自变量的每个元素都是第二个自变量的成员时),将满足该要求。例如:

 

?- subset([a,b],[a,b,c])

true

?- subset([c,b],[a,b,c])

true

?- subset([],[a,b,c])

true

 

您的程序应该能够通过回溯生成输入集的所有子集。例如,如果您将其作为输入

 

?- subset(X,[a,b,c])

 

它应该依次生成[a,b,c]的所有八个子集。

 

2.使用您刚编写的子集谓词和findall/3,编写一个谓词powerset/2,将该谓词powerset/2的第一个参数作为一个集合,并将该集合作为第二个参数返回。 (该集合是其所有子集的集合。)例如:

 

?- powerset([a,b,c],P)

 

将返回

 

P = [[],[a],[b],[c],[a,b],[a,c],[b,c],[a,b,c]]

 

集合是否以其他顺序返回也没关系。例如,

 

P = [[a],[b],[c],[a,b,c],[],[a,b],[a,c],[b,c]]

 

也可以

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值