Learn Prolog Now 翻译 - 第四章 - 列表 - 第三节,递归遍历列表

内容提要

通过递归对列表进行遍历,从而完成各种操作。

member/2这个谓词逻辑通过递归遍历了列表,对列表头部有一些操作,然后递归地对列表尾部做另外一些相同的操作。通过递归遍历列表在Prolog是十分普遍的做法,

事实上,我们必须要掌握这项技能。所以我们学习如下的例子。

当我们使用列表的时候,我们经常会将一个列表和另一个列表进行对比,或者拷贝一个列表的内容到另一个列表去,或者翻译一个列表到内容到另一个列表去,或者

类似到一些操作。这里有一个例子,假设我们有一个谓词a2b/2,有两个参数,第一个参数是a的列表,第二个参数是b的列表,而且两个列表的长度相同;比如,如果

我们进行查询:

?- a2b([a, a, a, a], [b, b, b, b]).

我们希望Prolog能够回答true。另外一方面,如果我们进行查询:

?- a2b([a, a, a, a], [b, b, b]).

或者进行查询:

?- a2b([a, c, a, a],  [b, b, 5, 4]).

我们希望Prolog能够回答false。

当我们面对此类任务时,通常最好的解决问题的方式是首先从最简单的情况入手。现在,当使用列表时,思考最简单的情况通常意味着从空列表开始,而且在这个例

子中确实也是有意义的。毕竟,什么样的列表是关于元素a最简单的列表?是空列表。为什么?因为它没有包含一个a元素。那么关于元素b最简单的列表呢?也是空列

表。所以我们能够定义出最基础的信息如下:

a2b([], []).

这个明确的事实记录了关于a的空列表和关于b的空列表时相等的。虽然这个事实很明确,但是它将会在程序中发挥至关重要的作用,我们稍后就会看到。

直到现在一切还好,那么如何进行下一步呢?这里有一个思路:对于更长的列表,通过递归去思考。所以,当谓词a2b/2去检查两个非空的列表,一个是关于a的,另

一个是关于b的,如何确认两个列表有相同的长度呢?很简单:当第一个列表的头部是a,同时第二个列表的头部是b,并且a2b/2能够证明两个列表的尾部有相同的长度,

我们就能够立刻写出如下的规则:

a2b([a | Ta], [b | Tb]) :- a2b(Ta, Tb).

解释一下:a2b/2能够成功的条件是,第一个参数是头部为a的列表,第二个参数是头部为b的列表,同时a2b/2能够在两个列表的尾部操作成功。

这个定义有了很好的声明性。这是一个简单而又自然的递归谓词,基础子句处理空列表,递归子句处理非空列表。但是它实际是如何工作的?即,它的程序性含义是怎么样的?

比如,如果我们查询:

?- a2b([a, a, a], [b, b, b]).

Prolog将会回答true,也是我们期望的,但是这一切是如何发生的?

让我们通过这个例子来学习。在例子中,两个列表都是非空的,所以事实子句不能提供帮助。所以Prolog就尝试使用递归规则,现在,查询能够满足这个规则(因为第一个列表的

头部是a,并且第二个列表的头部是b),所以Prolog有了新的目标,即:

a2b([a, a], [b, b]).

再一次地,事实子句不能提供帮助,但是递归规则能够再次被使用,导致接下来的目标是:

a2b([a], [b]).

事实子句还是不能提供帮助,但是递归规则可以,所以我们又有了如下的新目标:

a2b([], []).

最终我们可以使用事实了:它告诉我们true,我们确实有两个关于a和b的、长度相同的列表(空列表,什么都没有),这意味着如下的目标:

a2b([a], [b]).

也是成立的,这会导致目标:

a2b([a, a], [b, b]).

也是被满足的,所以原始的目标:

a2b([a, a, a], [b, b, b]).

是满足的。

我们总结这个过程如下:Prolog从两个列表开始,通过检查第一个列表的头部是否为a,第二个列表的头部是否为b,然后去掉两个列表的头部;而后,使用相同的处理方式对两个

列表的尾部进行操作。为什么这个过程会终止?因为每一次的递归后,列表都会变短,最终因为是空列表,所以会终止。从这个角度来说,程序中的事实会发挥决定性的作用:它给

出true的回答,并且终止了递归,从而确保原始的查询是成功的。

了解查询失败也是同样重要的。比如,如果我们查询:

?- a2b([a, a, a, a], [b, b, b]).

Prolog会正确地回答false,为什么?因为经过了去掉头部-循环尾部的处理过程三次后,会剩下如下的目标:

a2b([a], []).

但是这个目标无法满足。如果我们查询:

a2b([a, c, a, a], [b, b, 5, 4]).

经过去掉头部-循环尾部的处理过程仅仅一次,Prolog会有如下的新目标:

a2b([c, a, a], [b, 5, 4]).

同样地,这个目标也无法满足。

以上是a2b/2简单的使用情况,但是我们还没有完全覆盖完所有的使用场景。Prolog的使用过程中,查询输入变量始终是一个尝试的好方式。这时a2b/2会有一些有趣的事情发生,它

会表现得像一个转换器,将元素a的列表,转换为元素b的列表。比如查询:

?- a2b([a, a, a, a], X).

X = [b, b, b, b]

即,元素a的列表,已经被转换为元素b的列表。类似地,通过在第一个参数位置使用变量,我们可以将元素b的列表,转换为元素a的列表:

?- a2b(X, [b, b, b, b]).

X = [a, a, a, a]

你能够根据这个结果知道它是如何发生的吗?总结一下:a2b/2是一个很简单的、通过递归遍历列表的例子。但是不要被它的简单性迷惑:此类程序展示了Prolog的基础功能。无论是其

声明形式(一个处理空列表的基础子句,一个处理非空列表的递归子句),还是具体执行的程序性(在列表头部做一些操作,然后对其尾部进行同样的递归处理),都会在Prolog编程中

反复使用。事实上,在你的Prolog生涯中,你会发现你在写各式各样的a2b/2谓词,或者是其更复杂的变体,许多时候加入了很多的装饰,但是本质上是一样的。

时间: 2025-01-12 11:29:29

Learn Prolog Now 翻译 - 第四章 - 列表 - 第三节,递归遍历列表的相关文章

Learn Prolog Now 翻译 - 第四章 - 列表 - 第二节,列表成员

内容提要 本章主要介绍使用递归操纵列表的一个实际例子:判断一个元素是否在包含在一个列表中. 是时候介绍第一个Prolog中通过递归操纵列表的程序例子了.我们最感兴趣的事情之一是,某个对象是否是列表中的元素.所以,我们想写一个程序,当假设输入是一个对象X和一个列表L, 得出结果是X是否属于L.这个程序的名字通常是:member,是Prolog程序中使用递归操纵列表最简单的例子,如下: member(X, [X|T]). member(X, [H|T]) :- member(X, T). 这就是全部

Learn Prolog Now 翻译 - 第四章 - 列表 - 第一节,列表定义和使用

内容提要 列表定义: 合一在列表中的使用: 匿名变量: 列表定义 正如名字暗示的,列表就是多个元素组成的集合.更精确地说,是元素的有限序列.在Prolog中的列表,有如下的一些具体例子: [mia, vincent, jules, yolanda] [mia, robber(honey_bunny), X, 2, mia] [ ] [mia, [vincent, jules], [butch, girlfriend(butch)]] [[ ], dead(z), [2, [b, c]], [ ]

Learn Prolog Now 翻译 - 第四章 - 列表 - 第四节,练习题和答案

练习题4.1 Prolog将会如何回答下面的查询? 1. [a, b, c, d] = [a, [b, c, d]]. 2. [a, b, c, d] = [a | [b, c, d]]. 3. [a, b, c, d] = [a, b, [c, d]]. 4. [a, b, c, d] = [a, b | [c, d]]. 5. [a, b, c, d] = [a, b, c, [d]]. 6. [a, b, c, d] = [a, b, c | [d]]. 7. [a, b, c, d] =

Learn Prolog Now 翻译 - 第五章 - 数字运算 - 第二节,数字运算与列表

内容提要 列表中的一些数字运算,累加器 尾递归调用 列表中的一些数字运算,累加器 关于数字运算最为重要的应用,可能是获取一些数据结构体的一些有用事实,比如列表.例如,知道列表的长度是很有用的.我们将会给出一些使用列表和数字运算的例子. 一个列表的长度是多少?这里有一个递归定义: 1. 空列表的长度为0. 2. 非空列表的长度为 1 + len(T),其中len(T)是非空列表的尾部. 这个定义在Prolog中很容易实现,以下是实现代码: len([], 0). len([_|T], N) :-

Learn Prolog Now 翻译 - 第六章 - 列表补遗 - 第一节,列表合并

内容提要: 列表合并的定义 列表合并的使用 列表合并的定义 我们将会定义一个很重要的谓词:append/3,其中所有的参数都是列表.从声明性角度去看,append(L1, L2, L3)的含义是列表L3是列表L1和列表L2的合并结果(合并意味着连接).比如, 如果我们查询: ?- append([a, b, c], [1, 2, 3], [a, b, c, 1, 2, 3]). 或者查询: ?- append([a, [foo, gibble], c], [1, 2, [[], b]], [a,

Learn Prolog Now 翻译 - 第三章 - 递归 - 第一节,递归的定义

在Prolog中,谓词可以递归地定义.简要地讲,一个谓词是递归定义的,如果一个或者多个规则的定义中包含了规则自身. 例子1:消化 考虑如下的知识库: is_digesting(X, Y) :- just_ate(X, Y). is_digesting(X, Y) :- just_ate(X, Z), is_digesting(Z, Y). just_ate(mosquito, blood(john)). just_ate(frog, mosquito). just_ate(stork, frog

Learn Prolog Now 翻译 - 第五章 - 数字运算 - 第三节,整数的比较

内容提要 Prolog中如何进行整数的比较 整数比较的实际应用 Prolog中如何进行整数的比较 一些Prolog的运算谓词可以实际地进行运算(即,不需要通过“is”协助),这些运算谓词都是进行整数比较的操作符. 运算实例 Prolog表达式 x < y   X < Y. x ≤ y  X =< Y. x = y  X =:= Y. x /= y X =\= Y. x ≥ y   X >= Y. x > y X > Y. 这些操作符有明确的含义,可以直接在Prolog中

Learn Prolog Now 翻译 - 第三章 - 递归 - 第四节,更多的实践和练习

在学习了前三章内容后,我们应该对Prolog编程有了直观和理性的认识.由于合一.变量初始化.证明搜索和递归都是Prolog的核心概念,所以有如下更多的一些实践和练习. 这里我会先录入题目,后期再给出我自己的程序代码和一些思考. 实践1 试想有如下的描述迷宫的知识库.其中的事实描述了点和点之间的联通关系,即connected/2谓词逻辑给出了这样的事实:迷宫中能从参数1的点,直接到达参数2的点.而且, 联通关系是有方向的.单向不能往返的: connected(1,2). connected(3,4

Learn Prolog Now 翻译 - 第五章 - 数字运算 - 第四节,练习题和答案

练习题5.1  Prolog会如何回答下面的问题? 1. X = 3*4. 2. X is 3*4. 3. 4 is X. 4. X = Y. 5. 3 is 1+2. 6. 3 is +(1,2). 7. 3 is X+2. 8. X is 1+2. 9. 1+2 is 1+2. 10. is(X, +(1,2)). 11. 3+2 = +(3,2). 12. *(7,5) = 7*5. 13. *(7, +(3,2)) = 7*(3+2). 14. *(7, (3+2)) = 7*(3+2)