为了能够更好地表达一组数据,Prolog引入了列表(List)这种数据结构。 列表是一组项目的集合,此项目可以是Prolog的任何数据类型,包括结构和列表。列表的元素由方括号括起来,项目中间使用逗号分割。例如下面的列表列出了厨房中的物品。

[apple, broccoli, refrigerator]

我们可以使用列表来代替以前的多个子句。例如:

loc_list([apple, broccoli, crackers], kitchen).
loc_list([desk, computer], office).
loc_list([flashlight, envelope], desk).
loc_list([stamp, key], envelope).
loc_list(['washing machine'], cellar).
loc_list([nani], 'washing machine').

可见使用列表能够简化程序。

当某个列表中没有项目时我们称之为空表,使用“[]”表示。也可以使用nil来表示。下面的句子表示hall中没有东西。

loc_list([], hall)

变量也可以与列表联合,就像它与其他的数据结构联合一样。假如数据库中有了上面的子句,就可以进行如下的询问。

?- loc_list(X, kitchen).
X = [apple, broccoli, crackers]

?- [_,X,_] = [apples, broccoli, crackers].
X = broccoli

最后这个例子可以取出列表中任何一个项目的值,但是这种方法是不切实际的。你必须知道列表的长度,但是在很多情况下,列表的长度是变化的。

为了更加有效的使用列表,必须找到存取、添加和删除列表项目的方法。并且,我们应该不用对列表项目数和它们的顺序操心。

Prolog提供的两个特性可以方便的完成以上任务。首先,Prolog提供了把表头项目以及除去表头项目后剩下的列表分离的方法。其次,Prolog强大的递归功能可以方便地访问除去表头项目后的列表。

使用这两个性质,我们可以编出一些列表的实用谓词。例如member/2,它能够找到列表中的元素;append/3可以把两个列表连接起来。这些谓词都是首先对列表头进行处理,然后使用递归处理剩下的列表。

首先,请看一般的列表形式。

[X | Y]

使用此列表可以与任意的列表匹配,匹配成功后,X绑定为列表的第一个项目的值,我们称之为表头(head)。而Y则绑定为剩下的列表,我们称之为表尾(tail)。

下面我们看几个例子。

?- [a|[b,c,d]] = [a,b,c,d].
yes

上面的联合之所以成功,是因为等号两边的列表是等价的。注意表尾tail一定是列表,而表头则是一个项目,可以是表,也可以是其他的任何数据结构。下面的匹配失败,在“|”之后只能是一个列表,而不能是多个项目。

?- [a|b,c,d] = [a,b,c,d].
no

下面是其它的一些列表的例子。

?- [H|T] = [apple, broccoli, refrigerator].
H = apple
T = [broccoli, refrigerator]

?- [H|T] = [a, b, c, d, e].
H = a
T = [b, c, d, e]

?- [H|T] = [apples, bananas].
H = apples
T = [bananas]

?- [H|T] = [a, [b,c,d]]. 这个例子中的第一层列表有两个项目。
H = a
T = [[b, c, d]]

?- [H|T] = [apples]. 列表中只有一个项目的情况
H = apples
T = []

空表不能与[H|T]匹配,因为它没有表头。

?- [H|T] = [].
no

注意:最后这个匹配失败非常重要,在递归过程中经常使用它作为边界检测。即只要表不为空,那么它就能与[X|Y]匹配,当表为空时,就不能匹配,表示已经到达的边界条件。

我们还可以在第二个项目后面使用“|”,事实上,|前面的都是项目,后面的是一个表。

?- [One, Two | T] = [apple, sprouts, fridge, milk].
One = apple
Two = sprouts
T = [fridge, milk]

请注意下面的例子中变量是如何与结构绑定的。内部变量现实除了变量之间的联系。

?- [X,Y|T] = [a|Z].
X = a
Y = _01
T = _03
Z = [_01 | _03]

这个例子中,右边列表中的Z代表其表尾,与左边列表中的[Y|T]绑定。

?- [H|T] = [apple, Z].
H = apple
T = [_01]
Z = _01

上面的例子中,左边的表为T绑定为右边的表尾[Z]。

请仔细研究最后的这两个例子,表的联合对编制列表谓词是很有帮助的。

表可以看作是表头项目与表尾列表组合而成。而表尾列表又是由同样的方式组成的。所以表的定义本质上是递归定义。我们来看看下面的例子。

?- [a|[b|[c|[d|[]]]]] = [a,b,c,d].
yes

前面我们说过,列表是一种特殊的结构。最后的这个例子让我们对表的理解加深了。它事实上是一个有两个参数的谓词。第一个参数是表头项目,第二个参数是表尾列表。如果我们把这个谓词叫做dot/2的话,那么列表[a,b,c,d]可以表示为:

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

事实上,这个谓词是存在的,至少在概念上是这样,我们用“.”来表示这个谓词,读作dot。

我们可以使用内部谓词display/1来显示dot,它和谓词write/1大致上相同,但是当它的参数为列表时将使用dot语法来显示列表。

?- X = [a,b,c,d], write(X), nl, display(X), nl.
[a,b,c,d]
.(a,.(b,.(c,.d(,[]))))

?- X = [Head|Tail], write(X), nl, display(X), nl.
[_01, _02]
.(_01,_02)

?- X = [a,b,[c,d],e], write(X), nl, display(X), nl.
[a,b,[c,d],e]
.(a,.(b,.(.(c,.(d,[])),.(e,[]))))

从这个例子中我们可以看出为什么不使用结构的语法来表示列表。因为它太复杂了,不过实际上列表就是一种嵌套式的结构。这一点在我们编制列表的谓词时应该牢牢地记住。

我们可以很容易地写出递归的谓词来处理列表。首先我们来编写谓词member/2,它能够判断某个项目是否在列表中。

首先我们考虑边界条件,即最简单的情况。某项目是列表中的元素,如果此项目是列表的表头。写成Prolog语言就是:

member(H,[H|T]).

从这个子句我们可以看出含有变量的事实可以当作规则使用。

第二个子句用到了递归,其意义是:如果项目是某表的表尾tail的元素,那么它也是此列表的元素。

member(X,[H|T]) :- member(X,T).

完整的谓词如下:

member(H,[H|T]).
member(X,[H|T]) :- member(X,T).

请注意两个member/2谓词的第二个参数都是列表。由于第二个子句中的T也是一个列表,所以可以递归地进行下去。

?- member(apple, [apple, broccoli, crackers]).
yes

?- member(broccoli, [apple, broccoli, crackers]).
yes

?- member(banana, [apple, broccoli, crackers]).
no

下面是member/2谓词的单步运行结果。

我们的询问是
?- member(b, [a,b,c]).

1-1 CALL member(b,[a,b,c])
目标模板与第一个子句不匹配,因为b不是[a,b,c]列表的头部。但是它可以与第二个子句匹配。

1-1 try (2) member(b,[a,b,c])
第二个子句递归调用member/2谓词。

2-1 CALL member(b,[b,c])
这时,能够与第一个子句匹配了。

2-1 EXIT (1) member(b,[b,c])
于是一直成功地返回到我们的询问子句。

1-1 EXIT (2) member(b,[a,b,c])
yes

和大部分Prolog的谓词一样,member/2有多种使用方法。如果询问的第一参数是变量,member/2可以把列表中所有的项目找出来。

?- member(X, [apple, broccoli, crackers]).
X = apple ;
X = broccoli ;
X = crackers ;
no

下面我们将使用内部变量来跟踪member/2的这种使用方法。请记住每一层递归都会产生自己的变量,但是它们之间通过模板联合在一起。

由于第一个参数是变量,所以询问的模板能够与第一个子句匹配,并且变量X将绑定为表头。回显出X的值后,用户使用分号引起回溯,Prolog继续寻找更多的答案,与第二个子句进行匹配,这样就形成了递归调用。

我们的询问是
?- member(X,[a,b,c]).
当X=a时,目标能够与第一个子句匹配。

1-1 CALL member(_0,[a,b,c])
1-1 EXIT (1) member(a,[a,b,c])
X = a ;
回溯时释放变量,并且开始考虑第二条子句。

1-1 REDO member(_0,[a,b,c])
1-1 try (2) member(_0,[a,b,c])
第二层也成功了,和第一层相同。

2-1 CALL member(_0,[b,c])
2-1 EXIT (1) member(b,[b,c])
1-1 EXIT member(b,[a,b,c])
X = b ;
继续第三层,和前面相似。

2-1 REDO member(_0,[b,c])
2-1 try (2) member(_0,[b,c])
3-1 CALL member(_0,[c])
3-1 EXIT (1) member(c,[c])
2-1 EXIT (2) member(c,[b,c])
1-1 EXIT (2) member(c,[a,b,c])
X = c ;
下面试图找到空表中的元素。而空表不能与两个子句中的任何一个表匹配,所以查询失败了。

3-1 REDO member(_0,[c])
3-1 try (2) member(_0,[c])
4-1 CALL member(_0,[])
4-1 FAIL member(_0,[])
3-1 FAIL member(_0,[c])
2-1 FAIL member(_0,[b,c])
1-1 FAIL member(_0,[a,b,c])
no

下面再介绍一个有用的列表谓词。它能够把两个列表连接成一个列表。此谓词是append/3。第一个参数和第二个参数连接的表为第三个参数。例如:

?- append([a,b,c],[d,e,f],X).
X = [a,b,c,d,e,f]

这个地方有一个小小的麻烦,因为最基本的列表操作只能取得列表的头部,而不能在内表尾部添加项目。append/3使用递归地减少第一个列表长度的方法来解决这个问题。

边界条件是:如果空表与某个表连接就是此表本身。

append([],X,X).

而递归的方法是:如果列表[H|T1]与列表X连接,那么新的表的表头为H,表尾则是列表T1与X连接的表。

append([H|T1],X,[H|T2]) :- append(T1,X,T2)

完整的谓词就是:

append([],X,X).
append([H|T1],X,[H|T2]) :- append(T1,X,T2).

Prolog真正独特的地方就在这里了。在每一层都将有新的变量被绑定,它们和上一层的变量联合起来。第二条子句的递归部分的第三个参数T2,与其头部的第三个参数的表尾相同,这种关系在每一层中都是使用变量的绑定来体现的。下面是跟踪运行的结果。

我们的询问是:
?- append([a,b,c],[d,e,f],X).
1-1 CALL append([a,b,c],[d,e,f],_0)
X = _0
2-1 CALL append([b,c],[d,e,f],_5)
_0 = [a|_5]
3-1 CALL append([c],[d,e,f],_9)
_5 = [b|_9]
4-1 CALL append([],[d,e,f],_14)
_9 = [c|_14]
把变量的所有联系都考虑进来,我们可以看出,这时变量X有如下的绑定值。

X = [a|[b|[c|_14]]]
到达了边界条件,因为第一个参数已经递减为了空表。与第一条子句匹配时,变量_14绑定为表[d,e,f],这样我们就得到了X的值。

4-1 EXIT (1) append([],[d,e,f],[d,e,f])
3-1 EXIT (2) append([c],[d,e,f],[c,d,e,f])
2-1 EXIT (2) append([b,c],[d,e,f],[b,c,d,e,f])
1-1 EXIT (2)append([a,b,c],[d,e,f],[a,b,c,d,e,f])
X = [a,b,c,d,e,f]

和member/2一样,append/3还有别的使用方法。下面这个例子显示了append/3是如何把一个表分解的。

?- append(X,Y,[a,b,c]).
X = []
Y = [a,b,c] ;

X = [a]
Y = [b,c] ;

X = [a,b]
Y = [c] ;

X = [a,b,c]
Y = [] ;
no

使用列表

现在有了能够处理列表的谓词,我们就可以在游戏中使用它们。例如使用谓词loc_list/2代替原来的谓词location/2来储存物品,然后再重新编写location/2来完成与以前同样的操作。只不过是以前是通过location/2寻找答案,而现在是使用location/2计算答案了。这个例子从某种程度上说明了Prolog的数据与过程之间没有明显的界限。无论是从数据库中直接找到答案,或是通过一定的计算得到答案,对于调用它的谓词来说都是一样的。

location(X,Y):- loc_list(List, Y), member(X, List).

当某个物品被放入房间时,需要修改此房间的loc_lists,我们使用append/3来编写谓词add_thing/3:

add_thing(NewThing, Container, NewList):-
loc_list(OldList, Container),
append([NewThing],OldList, NewList).

其中,NewThing是要添加的物品,Container是此物品的位置,NewList是添加物品后的列表。

?- add_thing(plum, kitchen, X).
X = [plum, apple, broccoli, crackers]

当然,也可以直接使用[Head|Tail]这种列表结构来编写add_thing/3。

add_thing2(NewThing, Container, NewList):-
loc_list(OldList, Container),
NewList = [NewThing | OldList].

它和前面的add_thing/3功能相同。

?- add_thing2(plum, kitchen, X).
X = [plum, apple, broccoli, crackers]

我们还可以对add_thing2/3进行简化,不是用显式的联合,而改为在子句头部的隐式联合。

add_thing3(NewTh, Container,[NewTh|OldList]) :-
loc_list(OldList, Container).

它同样能完成我们的任务。

?- add_thing3(plum, kitchen, X).
X = [plum, apple, broccoli, crackers]

下面的put_thing/2,能够直接修改动态数据库,请自己研究一下。

put_thing(Thing,Place) :-
retract(loc_list(List, Place)),
asserta(loc_list([Thing|List],Place)).

到底是使用多条子句,还是使用列表方式,这完全有你的编程习惯来决定。有时使用Prolog的自动回溯功能较好,而有时则使用递归的方式较好。还有些较为复杂的情况,需要同时使用子句和列表来表达数据。 这就必须掌握两种数据表达方式之间的转换。

把一个列表转换为多条子句并不难。使用递归过程逐步地把表头asserts到数据库中就行了。下面的例子把列表转化为了stuff的一系列子句。

break_out([]).
break_out([Head | Tail]):-
assertz(stuff(Head)),
break_out(Tail).

?- break_out([pencil, cookie, snow]).
yes

?- stuff(X).
X = pencil ;
X = cookie ;
X = snow ;
no

把多条事实转化为列表就困难多了。因此Prolog提供了一些内部谓词来完成这个任务。最常用的谓词是findall/3,它的参数意义如下:

参数1: 结果列表的模板。
参数2: 目标模板。
参数3: 结果列表。

findall/3自动地寻找目标,并把结果储存到一个列表中。使用它可以方便的把stuff子句还原成列表。

?- findall(X, stuff(X), L).
L = [pencil, cookie, snow]

下面把所有与厨房相连的房间找出来。

?- findall(X, connect(kitchen, X), L).
L = [office, cellar, 'dining room']

最后我们再来看一个复杂的例子:

?- findall(foodat(X,Y), (location(X,Y) , edible(X)), L).
L = [foodat(apple, kitchen), foodat(crackers, kitchen)]

它找出了所有能吃的东西及其位置,并把结果放到了列表中。

Prolog教程 12相关推荐

  1. Python-EEG工具库MNE中文教程(12)-注释连续数据

    目录 注释连续数据(Annotating continuous data) 通过代码创建注释 可视化原始对象 交互式注释原始对象 对注释对象的操作 本分享为脑机学习者Rose整理发表于公众号:脑机接口 ...

  2. 黑马lavarel教程---12、lavarel验证码

    黑马lavarel教程---12.lavarel验证码 一.总结 一句话总结: 用插件的时候仔细看插件的版本要求 1.lavarel安装验证码插件的时候,如果(可选)需要定义自己的配置,则需要生成配置 ...

  3. NODE-WEBKIT教程(12)全屏

    node-webkit教程(12)全屏 文/玄魂 目录 node-webkit教程(12)全屏 前言 12.1  和全屏有关的三个api Window.enterFullscreen() Window ...

  4. STM32 电机教程 12 - BLDC 闭环电流控制

    前言 无刷直流 (Brushless Direct Current, BLDC)电机是一种正快速普及的电机类型,它可在家用电器.汽车.航空航天.消费品.医疗.工业自动化设备和仪器等行业中使用.正如名称 ...

  5. STM32 进阶教程 12 - M4的硬件乘法器使用

    前言 Cortex-M4支持硬件浮点运算单元(有些厂商直接说成DSP模块),本节将给大家介绍STM32F407芯片中硬件浮点运算单元中的使用及带来的性能对比. 示例详解 基于硬件平台: 正点源子的st ...

  6. STM32 基础系列教程 12 – ADC 中断

    前言 学习stm32 adc模数转换接口使用,学会用STM32对模拟信号时行采样,通过中断模式得到ADC结果. 示例详解 基于硬件平台: STM32F10C8T6最小系统板, MCU 的型号是 STM ...

  7. Atitit.5gl 第五代语言编程语言 PROLOG教程  人工智能语言的标准 与实现

    Atitit.5gl 第五代语言编程语言 PROLOG教程  人工智能语言的标准 与实现 1. 第五代语言就是自然语言又被称为知识库语言或人工智能语言,1 2. 人工智能语言特点2 2.1. 试探法2 ...

  8. Magento教程 12:Magento快速上传大量商品的方法

    利用.CSV档上传大量商品是最佳选择 在Magento上传大量商品到时,经常会遇到的几个问题,非常耗时间.出现错误.有部分商品是缺货状态. 当你有上百个不同属性的商品,而且只有部分商品是现货供应的情况 ...

  9. ArcGIS水文分析实战教程(12)河网分级流程

    ArcGIS水文分析实战教程(12)河网分级流程 本章导读:如果说河流提取是面对没有数据后者数据匮乏的用户,那么河网分级就完全属于为水文研究而生的一个工具.河流具有干流和支流之分,河网分级能够将这些干 ...

最新文章

  1. 27场机器学习面试后,来划个概念重点吧
  2. FPGA之道(84)功能仿真之Verilog Test Fixture
  3. 【Python 13】分形树绘制1.0--五角星(turtle库)
  4. Linux下的命令总结笔记(一)
  5. linux 内核驱动的名字,Linux内核驱动的的platform机制
  6. 在Windows Live Writer中插入C# code
  7. ubuntu上面svn用merge合并到之前的版本
  8. boostrap 鼠标滚轮滑动图片_16种基于Bootstrap的css3图片hover效果
  9. IntelliJ IDEA 2017新工具
  10. MIT6.830 lab5 B+ Tree Index 实验报告
  11. erlang 动态编译和加载遇到的问题。
  12. Python爬虫解析htm时lxml的HtmlElement对象获取和设置inner html方法
  13. 滴滴程序员年薪80万却被亲戚鄙视:钱多有啥用,我儿子二本大学教师
  14. 已知xyz yzz 532Java_已知 xyz+yzz=532 ,其中 x 、 y 、 z 为数字,编程求出 x 、 y 、 z 的值。_学小易找答案...
  15. Android P应用显示宽高比maxAspectRatio使用及原理
  16. 沐风老师3DMAX网球建模教程
  17. 精品微信小程序预约挂号小程序+后台管理系统|前后分离VUE
  18. 高效办公之超好用的流程图工具
  19. 自动生成卡密SQL脚本(转载)
  20. 跳出阿里之外做阿里之一------发布产品之外做阿里

热门文章

  1. [开心学PHP100天(七)]精讲PHP构造函数:父亲和儿子的恩恩怨怨
  2. buuctf 梅花香之苦寒来(根据坐标绘图)
  3. Excel条件格式,利用公式验证数据,对比数据差异
  4. 关于公众号wifi强制推送的一些想法
  5. 2020年Interbrand全球最佳品牌榜发布;SK海力士将以90亿美元收购英特尔NAND闪存及存储业务 | 美通企业日报...
  6. 简单的输出大写金额程序
  7. 2022年6月英语六级作文范文
  8. 清代黄自元 间架结构92法的现代文注释
  9. 【Oracle】输出和输入
  10. 厦大C语言上机 1488 判断素数