艾巴生活网

您现在的位置是:主页>科技 >内容

科技

prolog语言的工作原理_prolog中文教程及语法规则

2024-05-29 11:35:02科技帅气的蚂蚁
前言如果你是prolog的新手,希望你先看完这篇文章,以便更好的了解prolog的整体情况。本文将详细介绍prolog学习过程的编程思想和prolog语法

prolog语言的工作原理_prolog中文教程及语法规则

前言如果你是prolog的新手,希望你先看完这篇文章,以便更好的了解prolog的整体情况。本文将详细介绍prolog学习过程的编程思想和prolog语法的细节。

先问一个基本问题:我们在Prolog程序中见过很多类型的表达式(比如jody、playsAirGuitar(mia)、X),但这些只是例子。现在是深入的时候了。事实、规则和查询是由什么组成的?

答案是条款。Prolog中有四种类型的句子:原子、数字、变量和复句(或结构)。原子和数字统称为常数,常数和变量统称为简单句。

首先要明确基本字的范围:大写字母:A,B,Z;小写字母:a,b,z;数字:0,1,2, 9.此外,它还包括“_”,即英文下划线字符;以及其他一些特殊的英文字符,如:-,*,/,《,》,=,~;空格也是字符,但不常用,不可见。字符串是一个完整的字符序列。

原子(原子)

原子是下列之一:

1.由字符组成的字符串,其中有效字符包括:大写字母、小写字母、数字和下划线,小写字母用作起始字符。一些例子:butch,big_kahuna_burger,listen2Music,playsAirGuitar。

2.用单引号括起来的字符序列,如' Vincent '' The GIMP '' Five _ Dollar _ Shake '' %% @ # * '用单引号括起来的字符序列称为原子名。请注意,我们使用了空格字符。

其实单引号封装的作用之一就是可以在原子中准确使用空格字符等特殊字符。

3.由特殊字符组成的字符串,如@=,===,-都是原子。正如我们所看到的,一些特殊的原子,如;(逻辑OR),-(规则中连接头部和躯干的符号)具有预定义的含义。

数字(数字)

在典型的Prolog程序中,实数不是很有用,所以尽管大多数Prolog实现都支持浮点数,但本文不会讨论它们。

然而,整数(例如-2,-1,0,1,2,)都很有用。比如计算一个列表中元素的个数,我们会在第五章详细介绍。Prolog中数字的表示很简单,没有什么特别的,如下所示:

23,1001,0,-365,依此类推。

变量(变量)

变量是由大写字母、小写字母、数字和下划线组成的字符串,首字母必须是大写字母或下划线。例如:

X,Y,变量,_标签,X_526,列表,列表24,_头,尾,_输入,输出

都是Prolog中的有效变量。变量“_”是一个特例,叫做匿名变量,我们会在第四章介绍。

复杂术语

常量、数字和变量都是构建语句的模块。现在我们学习如何把它们变成复杂的语句。复杂语句也称为结构。

复杂语句由函数(也可以理解为函数名)和参数序列组成。参数序列放在括号中,用英文逗号分隔,放在函数后面。请注意,函数后面必须跟参数序列。

中间不能有空格。函子必须是原子,即变量不能作为函子使用。另一方面,参数序列可以是任何类型的语句。

从KB1到KB5,我们看到了很多复句的例子。比如playsAirGuitar(jody)是复句,其中PlaySailGuitar是函子,jody是参数序列(只有一个参数)。另一个

例子有loves(vincent,mia),loves是函子,vincent和mia是参数序列;另一个例子包括变量:嫉妒(马沙,W)。

(注:函子和谓词在某种程度上是不同的。我的理解是,函子是谓词的名称,谓词包含函子及其参数序列,是整个逻辑的实现统一。)

然而,复杂语句的定义可以允许更复杂的情况。事实上,其他复杂语句也可以嵌入到复杂语句中(即复杂语句允许递归)。例如:

hide(X,父亲(父亲(父亲(布奇))))。

这是一个完美的符合定义的复杂陈述。它的函子是hide,它有两个参数:一个是变量X,一个是复杂语句,father(父亲(Butch))。这个复杂的陈述包括:函子是父亲,

另一个复杂的语句,父亲(father(butch))是它唯一的参数。内层的复语句的参数还是复语句:父亲(butch)。但是在嵌套的最内层,参数是一个常量:butch。

事实上,这种嵌套(递归结构)使我们能够自然地描述许多问题,这种递归结构与变量unity的交互是Prolog的有力武器。

复杂语句中参数的数量称为arity。例如,woman(mia)是一个参数为1的复杂语句,loves(vincent,mia)是一个参数为2的复杂语句。

对于Prolog来说,参数非常重要。Prolog允许你用相同的仿函数和不同的参数定义复杂的语句。例如,我们可以用两个参数定义loves谓词,loves(vincent,Mia);还可以用三个参数定义loves谓词,

爱(文森特,马沙,米娅)。如果我们这样做,Prolog会认为这两个谓词是不同的。在第五章,我们将看到定义相同函子但有不同参数的具体应用。

当我们需要提到已定义的谓词并介绍如何使用它们时(比如在文档中),约定就是“函子/自变量”的形式。回到KB2,我们有三个谓词,表达如下:

听力2音乐

幸福的

playsAirGuitar

使用正式的写作方法如下:

收听2音乐/1

快乐/1

playsAirGuitar/1

Prolog不会因为定义了两个loves谓词而混淆,而是将loves/2和loves/3区分为不同的谓词。#p##e#

Prolog程序Prolog程序一般由一组事实、规则和问题组成,它们是程序执行的起点,称为程序的目标。

我们先写一个Prolog程序,如下:(为了方便引用,我们将这个程序称为“程序0”)

喜欢(铃声,运动)。

喜欢(玛丽,音乐)。

喜欢(玛丽,运动)。

喜欢(简、史密斯)。

朋友(约翰,X):-喜欢(X,读书),喜欢(X,音乐)。

朋友(约翰,X) :-喜欢(X,体育),喜欢(X,音乐)。-朋友(约翰,Y)。

接下来,我们来分析一下这个程序:

可以看出,这个节目有四个事实,两个规则,一个问题,其中事实、规则、问题都是分行写的;规则和事实可以连续排列在一起,其顺序可以任意排列,但谓词名称相同的事实或规则必须集中排列在一起;问题不能用规则和事实来安排。作为程序的目标,它们要么单独列出,要么在程序运行时临时给出。

这个程序的事实描述了一些对象(包括人和物)之间的关系;该规则描述了约翰交朋友的条件,即如果一个人喜欢阅读和音乐(或者运动和音乐),那么这个人就是约翰的朋友(当然这个规则也可以看作是约翰朋友的定义);节目中的问题是“约翰的朋友是谁?”

Prolog程序中的目标也可以更改,也可以包含多条语句(上例中只有一条)。如果有多个陈述,这些陈述称为子目标。例如,对于上面的程序,问题也可以是:

?-喜欢(玛丽,X)。

还是?-喜欢(玛丽,音乐)。

还是?-朋友(X,Y)。

还是?-喜欢(贝尔,运动),喜欢(玛丽,音乐),朋友(约翰,X)。

但是对于不同的问题,程序运行的结果一般是不一样的。

还应该注意,Prolog程序中的事实或规则通常被称为它们相应谓词的子句。例如,上面程序中的前四个句子是谓语likes的子句。Prolog规定,同一个谓词的子句应该排列在一起。从语句形式和程序构成来看,Prolog是基于Horn子句的逻辑程序。这个程序需要事实和规则来验证查询。即证明给定的条件子句和无条件子句与目标子句矛盾,或者证明程序中的子句集不可满足。这就是所谓的Prolog的声明性语义。

从Prolog的语句来看,Prolog语言的语法结构相当简单。但是因为它的语句是Horn子句,而且Horn子句的描述能力很强,所以Prolog的描述能力也很强。比如,当它的事实和规则描述了某一学科的公理,那么问题就是一个待证明的命题;当事实和规则描述了一定的数据和关系,那么问题就是数据查询语句;当事实和规则描述了某个领域的知识,那么一个问题就是运用这些知识解决的问题;当事实和规则描述了某个初始状态和状态变化规律,那么问题就是目标状态。因此,Prolog语言实际上是一种广泛使用的智能编程语言。从上面最后一个目标可以看出,与过程化语言相比,对于一个Prolog程序来说,它的问题相当于主程序,它的规则相当于子程序,它的事实相当于数据。

Prolog程序的运行机制要了解Prolog的运行机制,首先需要了解几个基本概念。

1、自由变量和约束变量

在Prolog中,无值变量称为自由变量,有值变量称为约束变量。当变量取某个值时,就说它被某个值约束了,或者说它被某个值约束了,或者说它被某个值实例化了。在程序运行期间,一个自由变量可以被实例化成为一个约束变量,反之亦然,一个约束变量可以被取消成为一个自由变量。

2、合二为一

两个谓词可以匹配成一个,这意味着两个谓词具有相同的名称、相同的参数项数、相同的对应参数类型,并且对应的参数项也满足以下条件之一。

如果两者都是常数,那么它们必须完全相同。

如果两者都是约束变量,则两个约束值必须相同。

如果其中一个是常数,另一个是约束变量,则Jordan值和常数必须相同。

其中至少有一个是自由变量。

例如,下面两个谓词:

prel("ob1","ob2",Z)

prel("ob1",X,Y)

只有当变量X约束为“ob2”且Y和Z的约束值相同或至少有一个是自由变量时,才匹配。

Prolog的匹配和积分与归结原理中的积分基本相同,但这里的积分也是一个运算。该操作可以将两个匹配的谓词,即自由变量和常数,或者两个自由变量组合起来,建立对应关系,使常数作为对应变量的约束值,使两个对应的自由变量始终一致,即如果其中一个受某个值的约束,另一个也受相同值的约束;反之,如果一个的值被释放,另一个的值也被释放。

3、回溯

所谓回溯,就是在程序运行过程中,当一个子目标无法满足时(即谓词匹配失败),控制会返回到之前已经满足的子目标(如果存在),撤销其相关变量的约束值,然后重新满足。成功后,又会达到原来的子目标。如果在失败的子目标之前没有子目标,控制将返回到子目标的下一个更高级别的目标(即子目标)

不懂也没关系。下面举几个例子,看完这个Prolog程序的运行过程你就明白了。

有了以上基本概念,下面介绍Prolog程序的运行过程。我们还是以上面给出的Prolog程序“Program 0”为例。

假设给定的查询是:

?-朋友(约翰,y)。(约翰的朋友是谁?)

那么解决方案的目标是:

朋友(约翰,Y)。

此时,系统扫描程序以找到可以匹配目标谓词的事实或规则头。显然,程序中的前四个事实无法匹配目标,第五个语句的左端是规则:

朋友(约翰,Y) :-喜欢(X,读书),喜欢(X,音乐)。

的头部可以与目标谓词匹配。但是,因为这个陈述是一个规则,所以如果要建立它的结论,就必须建立它的所有前提。因此,原始目标的解被转换成新目标的解:

喜欢(X,读书),喜欢(X,音乐)。

这实际上是一个解决方案,取消了规则头,目标子句变成了:

?-喜欢(X,读书),喜欢(X,音乐)。

现在子目标依次是:

喜欢(X,读书)和喜欢(X,音乐)

求解。

子目标的求解过程和主目标完全一样,就是从头扫描程序,不断测试匹配,直到匹配成功或者扫描整个程序。

可以看出,像(X,阅读)这样的第一个子目标的求解,由于事实和规则不匹配,立即失效,导致了规则:

朋友(约翰,X) :-喜欢(X,读书),喜欢(X,音乐)。

整体的失败。所以,我们的目标是:

喜欢(X,读书)和喜欢(X,音乐)

当它被取消时,系统回到原来的目标朋友(约翰,x)。此时,系统从刚才目标的匹配句(即第五句)开始继续扫描程序中的子句,试图再次匹配原来的目标,最后找到第六句的左半部分,即规则。

朋友(约翰,X) :-喜欢(X,体育),喜欢(X,音乐)。

的头部可以匹配目标谓词。但是因为这个语句是一个规则,所以原目标的解依次转化为子目标:

喜欢(X,体育)和喜欢(X,音乐)

子目标likes(X,sports)立即匹配程序中的事实,变量X约束为bell。所以系统继续解决第二个子目标。因为变量X被约束了,第二个子目标实际上变成了

喜欢(铃声、音乐)。

由于程序中没有fact likes(铃声、音乐),目标的求解失败。于是,系统放弃这个子目标,将变量X恢复为自由变量,然后回到第一个子目标,再次求解。因为系统已经记住了刚刚与第一个子目标的谓词匹配的事实的位置,所以再次求解时,会从下一个事实开始。很容易看到,当程序中的第三个事实被测试时,第一个子目标成功求解,变量X被约束到mary。这样,第二个次级目标就变成了:

喜欢(玛丽,音乐)。

再解决。这次是急功近利。

因为两个子目标求解成功,原目标朋友(john,y)也成功,变量y约束为Mary(y和x的合一关系)。所以,系统回答:

Y=玛丽

程序结束了。上述程序的执行过程如下图所示。图b。

上述程序的运行是一个通过推理实现的评估过程。

从上述程序的运行过程来看,Prolog程序的执行过程是一个演绎推理过程,其中推理方式为逆向推理,控制策略为深度优先和回溯机制,具体实现方法为:自顶向下匹配子句;从左至右选择子目标;生成的新子目标(解析后)总是被插入到被消除的目标中(即目标队列的左边部分)。Prolog的这种归结演绎方法称为SLD(定义子句的带选择函数的线性归结)归结,或SLD反驳-归结方法。这样,SLD解析就是Prolog程序的运行机制,也就是所谓的Prolog语言的过程语义。#p##e#

数据和表达式1、字段

(1)标准字段。

Turbo prolog没有定义变量的类型,只是描述了谓词中每一项的范围。从上面我们知道,Turbo prolog有五个标准字段:整数、实数、字符、字符串和符号。此外,它还有三个复合字段:结构、表和文件。

(2)结构。

结构,也称为复合对象,是Turbo prolog谓词中的特殊参数项(类似于谓词逻辑中的函数)。结构的一般形式是:

《函子》 ( 《参量表》 )1

其中,函数和参数的标识符与谓词相同。请注意,这意味着结构也可以包含结构。因此,复合对象可以表示树形数据结构。例如,以下谓词:

喜欢(“汤姆”,体育(足球,篮球,乒乓球))。

击中目标/标志

体育(足球、篮球、乒乓球)1

是一个结构,也就是一个复合对象。另一个例子是:

人(“张华”,学生(“清华大学”),地址(“中国”,“北京”)。

读书(王红,书(人工智能技术概论,西安电子科技大学出版社))。

朋友(父亲(“李”)、父亲(“赵”))123

这些谓词中有复合宾语。

程序中复合对象的描述需要分层进行。例如,对于上面的谓词:

喜欢(“汤姆”,体育(足球,篮球,乒乓球))。

在程序中可以解释如下:

名称=符号

sy=符号

sp=运动(sy,sy,sy)

断言

喜欢(名字,sp)123456

(3)表。

表格的一般形式是:

[x1,x2,xn]1

其中xi(i=1,2,n)是一个Prolog项,一般要求同一个表的元素必须属于同一个字段。没有任何元素的表称为空表,记为[]。例如,以下是一些合法的表格。

[1,2,3]

[苹果、橘子、香蕉、葡萄、甘蔗]

["Prolog","MAENS","PROGRAMMING","in logic"]

[[甲,乙],[丙,丁],[戊]]

[]12345

表格的最大特点是它的元素数量可以在程序运行过程中动态变化。一个表的元素也可以是结构,也可以是表,此时它们的元素可以属于不同的字段。例如:

[姓名(“李明”)、年龄(20)、性别(男)、地址(西安)]

[[1, 2], [3, 4, 5], [6, 7]]12

都是合法的表。后一个示例显示了表也可以嵌套。

其实表是一种特殊的结构,是递归结构的另一种表现形式。这个结构的函数名取决于具体的Prolog版本,所以我们在这里用一个点来表示。以下是一些这样的结构及其表表达式:

结构形式表格形式

(甲,[])[甲]

(甲,(乙,[])[甲,乙]

(a,(b,(c,[]))[a,b,c]

表格的解释方法是在其组成元素的描述符后加一个星号*。例如:

列表=字符串*

断言

pl(列表)1234

这意味着谓词pl中的列表是一个由字符串组成的表。

对于结构组成的表,至少分三步解释。比如下面P中的表格。

对于结构组成的表,至少分三步解释。比如下面P中的表格。

p([姓名(“黎明”),年龄(20)])1

你需要解释一下:

rec=seg*

seg=name(字符串);年龄(整数)

断言

第12345号建议

2、常量和变量

根据以上字段,Turbo Prolog的常量有八种数据类型:整数、实数、字符、字符串、符号、结构、表格、文件。同样,Turbo Prolog的变量也有这八个值。另外,变量名必须是以大写字母或下划线开头的字母、数字和下划线的序列,或者只有一个下划线(这样的变量称为无名变量)。

3、算术表达式

Turbo Prolog提供了五种基本的算术运算:加、减、乘、除、取模,对应的运算符号有""、-"、*"、/"和"mod"。这五个操作的顺序是:*“、/”和“mod”优先于“-”。

算术表达式的形式和数学中的基本相同。例如:

数学中的算术表达式Turbo Prolog中的算术表达式

x yzX Y * Z

ab - c/dA * B - C/D

U mod vU mod V(表示u除以V得到的余数)

即Turbo Prolog中的算术表达式采用数学中常用的中缀形式。这个算术表达式是Prolog的变体结构。如果用Prolog的结构形式表达,它们应该是:

(X,*(Y,Z))

-(*(A,B),/(C,D))

mod(U,V)123

所以运算符""-""、*、/"和"mod"实际上是Prolog中定义的函数符号。

在Turbo Prolog程序中,如果一个算术表达式中的所有变量都被实例化(即被约束),就会找到该算术表达式的值。找到的值可用于实例化变量或与其他量进行比较。用算术表达式的值实例化变量的方法是通过谓词“is”或“=”实现的。例如:

Y是X ^ 5或Y=X ^ 5(*)123

将变量y实例化为X ^ 5的值(当然X也必须实例化某个值)。可以看出,这里变量y的实例化方法类似于其他高级编程语言中的“赋值”,但又不同于赋值。例如,在Prolog中,下面的公式是错误的:

X=X 11

需要注意的是,Prolog虽然是一种逻辑编程语言,但是在目前的硬件条件下,需要突破逻辑框架。这是因为有些实际操作无法用逻辑描述(比如输入输出),有些算术运算原则上可以用逻辑描述,但效率太低。因此,Prolog提供了几个内部谓词(也称为预定义谓词)。实现算术运算、输入输出运算。所谓内部谓词,就是用户事先在实现语言中定义好的,可以作为子目标直接调用的谓词。通用Prolog实用系统配备了100多个内部谓词,涉及输入输出、算术运算、搜索控制、文件操作和图形声音等。它们对于实际的Prolog编程是必不可少的。因此,以上(*)

4、关系表达式

Turbo Prolog提供了六种常见的关系运算,即小于、小于或等于、等于、大于、大于或等于和不等于,它们的运算符是:

《, 《=, =,》 ,》=, 《》 1

Turbo Prolog的关系表达式的形式与数学中的基本相同,例如:

Turbo Prolog中的数学关系

x1yx1"=Y

X YX 《》 Y

即Turbo Prolog中的关系也是中缀形式。当然,这个关系在Turbo Prolog中是外来原子。如果在Turbo Prolog中用原子形式表示,上面两个例子如下:

=(X 1,Y)和《》 (X,Y)1

所以上面的六个关系运算符实际上就是Turbo Prolog内部定义的六个谓词。这六个关系运算符可用于比较两个算术表达式的大小。例如:

兄弟(姓名1,姓名2) :-人(姓名1,男性,年龄1),

人(姓名2,男性,年龄2),

母亲(Z,名字1),母亲(Z,名字2),年龄1”年龄2.123

需要注意的是,“=”的用法比较特殊。它可以同时表示比较值和约束值,甚至可以表示同一个规则中的同一个“=”。例如:

p(X,Y,Z) :- Z=X Y.1

当变量X,Y,Z都被实例化后,“=”就是比较器。例如,对于这个问题:

目标:p(3,5,8).1

机器回答“是”,对于:

目标:p(3,5,7).1

机器回答“不”。也就是此时机器比较X Y的值和Z的值,然而当X Y被实例化而Z没有被实例化时,“=”号就是约束,比如:

目标:P(3,5,Z).1

机器回答“Z=8”。此时,机器将Z实例化为X Y的结果.

输入输出虽然Prolog可以自动输出目标子句中变量的值,但这种输出功能必然是有限的,往往不能满足实际需要。此外,对于大多数程序来说,在运行时从键盘输入相关数据或信息也是必不可少的。出于这个原因,每个特定的Prolog一般都提供特殊的输入和输出谓词,供用户直接调用。例如,以下是Turbo Prolog的几个输入和输出谓词:

Readin(X)。这个谓词的功能是从键盘上读取一个字符串,然后将它约束到变量x .

Readint(X)。这个谓词的作用是从键盘上读取一个整数,然后将其约束到变量x,如果不是键盘上键入的整数,谓词就失败。

Readreal(X)。这个谓词的作用是从键盘上读取一个实数,然后约束到变量x,如果不是在键盘上键入的实数,这个谓词就失败了。

Readchar(X)。这个谓词的作用是从键盘上读取一个字符,然后将其约束到变量x上,如果键盘上没有键入单个字符,该谓词就失败了。

写(X1,X2,Xn)。这个谓词的功能是显示项目Xi(i=1,2,n)在屏幕上或打印在纸上。当Xi没有被实例化时,谓词会失败。其中Xi可以是变量、字符串或数字。例如:

写(“计算机”,“Prolog”,Y,1992)

Nl(换行谓词)。它使下面的输出(如果有的话)开始一个新行。此外,使用write的输出项""还可以起到换行符的作用。例如:

写(“姓名”),nl,写(“年龄”)

写(“姓名”、“年龄”)

效果完全一样。

例如:

用上面的输入输出谓词为学生成绩数据库写一个简单的查询程序。

断言

学生(整数、字符串、实数)

等级

目标

等级。

条款

学生(1,张三,90.2)。

学生(二,“李四”,95.5)。

学生(3,王武,96.4)。

年级:- write("输入姓名:"),readln(姓名),

学生(_,姓名,分数),

Nl,写(名字,“分数是”,分数)。

年级:-写(“对不起,我找不到这个学生!”).12345678910111213

以下是程序运行时的屏幕显示

请输入你的名字:王武。

王五的分数是96.412。

四、分支和循环

Prolog原本没有分支和循环语句,但它的逻辑机制可以用来达到分支和循环的效果。

1、分行

对于通常的IF-THEN-ELSE分支结构,Prolog可以由两个平行的规则用相同的头来实现(相同的头意味着相同的结论)。例如:

如果X >0,那么X:=1

否则X:=012

使用Prolog是:

br:-X"0,X=1。

br :- X=0.12

同样,对于多分支,可以通过多个规则来实现。例如:

br:-X"0,X=1。

br :- X=0,X=0。

br :- X 《0,X=-1.123

2、周期

Prolog可以实现FOR循环计数和DO循环计数。

例如:

下面的程序段实现了循环,使得write语句重复三次,打印出三个学生的记录:

学生(1,张三,90.2)。

学生(二,“李四”,95.5)。

学生(3,王武,96.4)。

打印:-学生(编号、姓名、分数),

写(数字,名字,分数),nl,

数字=3.123456

可以看到,程序第一次执行时,学生谓词匹配第一个事实,write语句输出张三同学的记录。但当程序执行到最后一句时,由于数字不等于3,语句失败,从而造成回溯。write语句和nl语句只能执行一次,所以我们继续回溯到student语句。这样,学生谓词由于失败而被重新匹配。这一次匹配了第二个事实,导致了李四的战绩输出。同样,最后一句执行的时候,引起了回溯。第三次执行write语句后,因为数字等于3,最后一条语句成功,程序段结束。

这个例子可以看作是一个计数循环。当然,真正的计数周期也可以通过设置计数器来实现。下面的程序段实现了一个不计数的DO循环:

学生(1,张三,90.2)。

学生(二,“李四”,95.5)。

学生(3,王武,96.4)。

打印:-学生(编号、姓名、分数),

写(数字,名字,分数),nl,

失败。

打印:-.1234567

这个程序段中的fail是一个内部谓词,它的语义是不断失败。这个程序段和上面的程序段唯一不同的是,把原来的带计数器(或标记号)的循环控制语句改成了常量失败谓词fail,并增加了一个打印语句,增加这个语句是为了给程序设置一个出口。因为fail是不断的失败,如果下面没有出口,就会造成打印本身的失败,从而导致程序的连锁失败。

还需要注意的是,Prolog的递归机制也可以用来实现循环,但是递归通常需要和表配合使用。另外,递归有容易造成内存溢出的缺点,所以通常的循环大多是通过上述方法实现的。

动态数据库动态数据库是在内存中实现的动态数据结构。它由事实组成,可以由程序操作,所以在程序运行过程中可以动态变化。Turbo Prolog提供了三个动态数据库操作谓词,即:

阿塞塔(《fact》)

阿瑟茨(《fact》)

缩回(《fact》 )123

事实代表事实。这三个谓词的功能如下:

Asserta( 《fact》)在当前动态数据库中同名谓词的事实前插入事实。

Assertz( 《fact》)将事实插入到当前动态数据库中同名的谓词中。

收回(《fact》)从当前动态数据库中删除事实。

例如,语句:

阿塞塔(学生(20,“李明”,90.5)).1

将在内存中名为student的谓词之前插入一个新事实:

学生(20,'李明'90.5)1

如果记忆中没有这个事实,那就是第一个。

收回(学生(20,_,_)).1

要从内存中的动态数据库中删除的事实:

学生(20,_,_)1

可以解释为一个学号为20的学生的记录。请注意,这里使用了未命名的变量“_”。

可以看出,Turbo Prolog提供的动态数据库机制可以轻松实现堆栈、队列等动态数据结构,提供的数据库操作谓词大大简化了编程。

此外,Turbo Prolog提供了两个文件操作谓词:

保存(《filename》)。

请咨询(《filename》)。12

保存可以将当前内存中的事实保存到文件"文件名"中,而查阅会将文件"文件名"中的事实转移到内存中。#p##e#

表格处理和递归1、页眉和页脚

表是Prolog中非常有用的数据结构。它有很强的表达能力。常用语言中的数字、数组和记录的顺序和集合可以用表格来表示。表格最大的特点是长度不固定,在程序运行过程中可以动态变化。具体来说,当程序运行时,可以对表执行一些操作,比如向表中添加一个元素,从表中删除一个元素,或者添加两个表。

表的另一个重要特征是它可以分为头和尾。页眉是表格中的第一个元素,而页脚是由表格中除第一个元素之外的其他元素按照原始顺序组成的表格。例如,下表是一个示例。

表格页眉和页脚

[1, 2, 3, 4, 5] 1 2, 3, 4, 5]

苹果,橘子,香蕉苹果,橘子,香蕉

[[a,b],[c],[d,e]] [a,b] [[c],[d,e]]

["Prolog"]"Prolog"[]

[]无定义无定义

* *页眉页脚示例* * Table2、**表格的匹配* *在程序中,|用于区分页眉页脚,也可以使用变量。例如,`[h | t]`通常用于表示表格,其中h和t是变量,h是页眉,t是页脚。t是一个表(除了第一个元素出现时其他元素的原始顺序)。表格的这种表示非常有用。为表格的操作提供了极大的便利。如下表所示,是通过匹配合并提取页眉页脚的例子。|表1 |表2 |合并后的变量值| |:——3354-:|:————-:|:——3354-:| | | |[x | y]。Y=[b,c] | | [X | Y] | [a] | X=a,Y=[] | | [a | Y] | [X,b] | X=a,Y=[b]|[X,Y,Z] | [a,b,c] | X=a,Y=b,Z=c | | [[a,Y] | Z] | [[X,b],[c]] | X=a,Y=b,Z=[[c]] |

* *表匹配和集成的示例* *还应注意,表中“|”后只能有一个变量。比如[[X | Y,z]的写法不对,但是竖线前可以有多个变量。例如,允许书写[x,Y | Z]]。这样,表与[a,b,c]匹配后,

X=a,Y=b,Z=[c]1

另外竖线的前后也可以是常量,比如[a | y]和[[X | b]]都是允许的,但是需要注意的是后一种表叫做无尾表。如果与表[a | y]匹配,则有:

X=a,Y=b(而不是y=[b]) 1

如果没有|,页脚不能分开。比如表[x,Y,z]和[a,b,c]组合后,X=a,Y=b,Z=c,其中变量Z不等于[c]。

接下来,我们将通过三个示例更详细地理解表的操作。

例6-1:设计一个程序,可以判断对象X是表l的成员.

我们可以这样想象:

如果X和表L中的第一个元素(表头)是同一个对象,那么X就是L的成员;

如果X是L的尾的成员,那么X也是L的成员.

根据这种逻辑关系,有以下几种Prolog程序:

成员(X,[X | Tail])。

member(X,[Head | Tail]) :- member(X,Tail).12

第一个从句的语义是上面的第一句话;第二个分句的语义就是上面的第二句。可以看出这个程序中使用了递归技术,因为谓词成员的定义包含了自身。用这个程序可以判断任意给定对象和表之间是否存在成员(member)关系。例如,取表L为[a,b,c,d],x为a,向上面的程序提出以下问题:

目标:成员(a,[a,b,c,d])。1

回答“是”。同样对于查询:

目标:成员(b,[a,b,c,d])。

目标:成员(c,[a,b,c,d])。

目标:成员(d,[a,b,c,d])。123

回答“是”。但是如果问:

目标:成员(e,[a,b,c,d])。1

然后回答“不”。如果我们问:

目标:成员(X,[a,b,c,d])。1

意思是证明这样一个X的存在,这个X是表的成员。这时,系统返回X的值,即:

X=a1

如果需要,系统还会给出x的所有其他值。

例6-2:编写一个表格拼接程序,即将两个表格连接成一个表格。

追加([],L,L)。

append([H | T],L2,[H | Tn]) :- append(T,L2,Tn)

程序中第一个子句的意思是将一个空表与任意一个表L拼接的结果仍然是表L;第二个子句意味着将非空表L1与另一个表L2拼接的结果L3是这样一个表,其头部是L1的头部,其尾部是将L1的尾部T与L2拼接的结果Tn。这个程序描述了两个表和它们的拼接表之间的逻辑关系。

可以看出谓词append是递归定义的,子句append ([],l,l)是最后的条件,也就是递归exit。

对于这个程序,如果我们问:

目标:追加([1,2,3],[4,5],L).1

然后系统递归调用程序中的第二个子句三次,最后从第一个子句终止,然后逆序算出每个拼接表,最后输出:

L=[1,2,3,4,5]1

当然,你也可以问关于这个节目的其他问题,比如:

目标:追加([1,2,3],[4,5],[1,2,3,4,5])。

系统回答是。

目标:追加([1,2,3],[4,5],[1,2,3,4,5,6])。

系统回答没有。

目标:追加([1,2,3],Y,[1,2,3,4,5])。1

系统回答x=[4,5]。

目标:append(X,[4,5],[1,2,3,4,5])1

系统回答x=[1,2,3]。

目标:append(X,Y,[1,2,3,4,5])。1

系统回答

X=[],Y=[1,2,3,4,5]

X=[1],Y=[2,3,4,5]

X=[1,2],Y=[3,4,5]

X=[1,2,3],Y=[4,5]

等等。(如果所有的解决方案都需要的话)。38960 . 68668686661

示例6-3:表格输出

打印([])。

print([H | T]) :- write(H),print(T).12

例6-4:一个表的逆序,即求一个表的逆序表。

反转([],[])。

reverse([H | T],L) :- reverse(T,L1),append(L1,[H],L).12

这里逆向的第一项是输入,也就是原表;第二项是输出,是原始表的倒置。

回溯控制Prolog在搜索目标解的过程中,有一个回溯机制,即当一个子目标“Gi”不能满足时,它会返回到该子目标的前一个子目标“Gi-1”并放弃“Gi-1”的当前约束值,使其重新匹配。在实际问题中,有时不需要回溯。因此,在Prolog中专门定义了一个防止回溯的内部谓词。称为截断谓词。

截断谓词的语法格式很简单,就是感叹号“!”。的语义如下。

如果"!"作为插入子句体的子目标,它总是立即成功。

如果"!"在子句体的末尾,它阻止回溯访问其子句的头谓词的所有子句,并允许回溯跳过头谓词(子目标)访问前一个子目标(如果有的话)。

如果"!"在其他位置,当回溯发生在之后并且回溯到“!”当时,它在这里失败了,而且!还会使其所在子句的head谓词(子目标)完全失效(即阻止访问head谓词的其余子方向(如果有的话),也就是强制系统直接回溯到head谓词(子目标)的前一个子目标(如果有的话)。

例如:

考虑以下过程

p(a)。(7 - 1)

p(b)。(7 - 2)

q(b)。(7 - 3)

r(X) :- p(X),q(X)。(7 - 4)

第12345号共和国法令

对于目标:r(X)。可以有一个解决方案:

Y=b

但是当我们将公式(7-4)改为:

r(X) :- p(X),q(X)。(7 - 4')1

时,却无解。为什么?

这是因为添加了截断谓词“!”因为在解方程(7-4’)中的子目标p(X)时,X被约束为A,然后“!”被跳过。然而,我在解决子目标q(a)时遇到了麻烦,所以我回到了“!”还有“!”对p(X)的下一个子句p(b)和R的下一个定义子句r(c)的访问被阻塞,导致整个解的失败。

再举一个例子:

有程序:

g0 :- g11,g12,g13。(7 - 5)

g0 :- g14。(7 - 6)

g12 :- g21,g23。(7 - 7)

g12 :- g24,g25。(7 - 8)

.12345

给目标:g0。

假设它没能跑到子目标g23,那么如果没有“!”在第(7-7)条中,如果g21失败,将进入下面的第(7-8)条。然而,因为“!”存在,所以我们不能回到g21,直接宣布g12失败。所以第(7-5)条回到g11。如果我们将条款(7-7)改为:

g12 :- g21,g23,(7 - 9)1

当然,这个

八、程序举例

下面给出几个简单而又典型的程序实例.通过这些程序,读者可以进一步体会和理解 Prolog 程序的风格和能力,也可以掌握一些基本的编程技巧.

例8-1:下面是一个简单的路径查询程序.程序中的事实描述了如下图所示的有向图,规则是图中两节点间有通路的定义.

predicates

road(symbol, symbol)

path(symbol, symbol)

clauses

road(a, b).

road(a, c).

road(b, d).

road(c, d).

road(d, e).

road(b, e).

path(X, Y) :- road(X, Y).

path(X, Y) :- road(X, Z), path(Z, Y).123456789101112

程序中未含目标,所以运行时需给出外部目标.例如当给出目标:

path(a, e).1

时,系统将回答yes,但当给出目标:

path(e, a).1

时,系统则回答no,如果给出目标:

run.1

且在程序中增加子句:

run :- path(a, X), write(”X =“, X), nl, fail.

run.12

屏幕上将会输出:

X = b

X = c

X = d

X = e

X = d

X = e

X = e1234567

即从 a 出发到其他节点的全部路径.

例8-2:下面是一个求阶乘程序,程序中使用了递归.

domains

n, f = integer

predicates

factorial(n, f)

goal

readint(I),

factorial(I, F),

write(I, ”!=“, F).

clauses

factorial(1, 1).

factorial(N, Res) :-

N》 0,

N1 = N - 1,

factorial(N1, FacN1),

Res = N * FacN1.12345678910111213141516

程序运行时,从键盘上输入一个整数,屏幕上将显示其阶乘数.

例8-3:下面是一个表的排序程序,采用插入排序法.

domains

listi = integer*

predicates

insert_sort(listi, listi)

insert(integer, listi, listi)

asc_order(integer, integer)

clauses

insert_sort([], []).

insert\_sort([H | Tail], Sorted_list) :-

insert_sort(Tail, Sorted\_Tail),

insert(H, Sorted_Tial, Sorted\_list).

insert(X, [Y | Sorted_list1]) :-

asc_order(X, Y), !,

insert(X, Sorted_list, Sorted\_list1).

insert(X, Sorted_list, [X | Sorted\_list]).

asc_order(X, Y) :- X》 Y.1234567891011121314151617

程序中对表的处理使用了递归.程序中也未给出目标,需要在运行时临时给出.例如当给出目标:

insert_sort([5, 3, 4, 2, 6, 1, 7, 8, 9, 0], L).1

时,系统将输出:

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]1