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

内容提要

列表中的一些数字运算,累加器

尾递归调用

列表中的一些数字运算,累加器

关于数字运算最为重要的应用,可能是获取一些数据结构体的一些有用事实,比如列表。例如,知道列表的长度是很有用的。我们将会给出一些使用列表和数字运算的例子。

一个列表的长度是多少?这里有一个递归定义:

1. 空列表的长度为0.

2. 非空列表的长度为 1 + len(T),其中len(T)是非空列表的尾部。

这个定义在Prolog中很容易实现,以下是实现代码:

len([], 0).

len([_|T], N) :- len(T, X), N is X + 1.

这个谓词会如期望的运行,比如:

?- len([a, b, c, d, e, [a, b], g], X).

X = 7

这是一个不错的程序:很容易理解,并且很书写很高效。但是还有其他一些求列表长度的方式。我们将会学习这种替代方式,因为它会引入累加器的概念。如果你有使用其他编程语言的经验,

你可能已经知道使用变量保持中间结果的概念,累加器就是Prolog中对应的思路。

如下是一个使用累加器计算列表长度的例子,我们将定义一个谓词,accLen/3,有如下的参数:

accLen(List, Acc, Length)

这里的List就是我们想要求解的列表,Length就是列表的长度(是一个整数)。Acc是什么?就是我们用于保存长度中间值的累加器(所以也是一个整数)。我们的思路是,如果我们调用这个

谓词,将Acct初始化为0;当递归对列表进行操作时,每当找到一个头元素,就将Acct加1,直到列表为空;当列表为空时,Acc就会保存列表的长度,下面是代码:

accLen([_|T], A, L) :- Anew is A+1, accLen(T, Anew, L).

accLen([], A, A).

关于基础子句的定义,将第二个参数和第三个参数进行了合一。为什么?因为这个简单的合一是返回结果的良好方式。当达到了列表的底部,累加器(第二个参数)持有了列表的长度值,所以

所以将这个值通过合一赋予长度变量(第三个参数)。下面是一个例子的追踪,可以清晰地看到当Prolog到达列表底部,长度变量通过合一进行了赋值:

?- accLen([a, b, c], 0, L).

  Call: (6) accLen([a, b, c], 0, _G499) ?

  Call : (7) _G518 is 0 + 1 ?

  Exit:  (7) 1 is 0 + 1 ?

  Call: (7) accLen([b, c], 1, _G499) ?

  Call: (8) _G521 is 1+1 ?

  Exit: (8) 2 is 1+1 ?

  Call: (8) accLen([a], 2, _G499) ?

  Call: (9) _G524 is 2+1 ?

  Exit: (9) 3 is 2+1?

  Call: (9) accLen([], 3, _G499) ?

  Exit: (9) accLen([], 3, 3) ?

  Exit: (8) accLen([c], 2, 3) ?

  Exit: (7) accLen([b, c], 1, 3) ?

     Exit: (6) accLen([a, b, c], 0 ,3)?

最后,我们可以定义一个谓词调用accLen,并且给出累加器的初始值为0:

leng(List, Length) :- accLen(List, 0, Length).

所以,我们可以进行如下的查询:

?- leng([a, b, c, d, e, [a, b], g], X).

X = 7

尾递归调用

累加器在Prolog中是很常用的(后面的章节会看到更多使用累加器的例子),但是为什么会这样?accLen在哪个方面比len更好呢?毕竟,accLen看上去更加复杂。答案就是因为accLen是

尾递归调用,但len不是。在一个尾递归调用的程序里,当递归到底底部的时候,结果已经计算得出,剩下所需要做的,就是逐层返回。在一个不是尾递归调用的递归中,一层的目标会等待

更里层的结果返回后,再进行计算。为了更清楚地理解,可以对比查询accLen([a, b, c], 0, L)的追踪,和查询len([a, b, c], L) (如下所示):

?- len([a, b, c], L).

  Call: (6) len([a, b, c], _G418) ?

  Call: (7) len([b, c], _G481) ?

  Call: (8) len([c], _G486) ?

  Call: (9) len([], _G489) ?

  Exit: (9) len([], 0) ?

  Call: (9) _G486 is 0+1 ?

  Exit: (9) 1 is 0+1 ?

   Exit: (8) len([c], 1) ?

   Call: (8) _G481 is 1+1 ?

   Exit: (8) 2 is 1+1?

   Exit: (7) len([b, c], 2) ?

   Call: (7) _G418 is 2+1 ?

   Exit: (7) 3 is 2+1 ?

   Exit: (6) len([a, b, c], 3) ?

在accLen的查询追踪里,当递归到底底部,accLen([], 3, _G449),结果就已经计算完毕,剩下只是回传上去。在len的查询追踪里面,结果的计算依赖递归,比如,len([b, c], _G481)的结果,

只能在完成len([c], _G489)的结果后才能进行计算。简而言之,尾递归程序会有更少的中间变量回溯计算,这使得递归会更高效。

时间: 2024-10-18 09:46:01

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

Learn Prolog Now 翻译 - 第五章 - 数字运算 - 第一节,Prolog中的数字运算

内容提要 Prolog中的数字运算 Porlog运算的本质 Prolog中的数字运算 Prolog语言本身提供了一些基础的运算符号,对整数进行一些操作(即类似...-3, -2, -1, 0, 1, 2, 3等).多数Prolog的实现同时也提供了一些工具对实数进行操作 (比如浮点数,1.53,6.35,等等).但是我们不会讨论浮点数,因为浮点数在典型的Prolog应用中很少,所以不是本文的重点.但是另一方面,整数是在Prolog 中有典型应用场景的(比如记录列表的长度),所以掌握起应用是十分重

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)

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 翻译 - 第四章 - 列表 - 第三节,递归遍历列表

内容提要 通过递归对列表进行遍历,从而完成各种操作. member/2这个谓词逻辑通过递归遍历了列表,对列表头部有一些操作,然后递归地对列表尾部做另外一些相同的操作.通过递归遍历列表在Prolog是十分普遍的做法, 事实上,我们必须要掌握这项技能.所以我们学习如下的例子. 当我们使用列表的时候,我们经常会将一个列表和另一个列表进行对比,或者拷贝一个列表的内容到另一个列表去,或者翻译一个列表到内容到另一个列表去,或者 类似到一些操作.这里有一个例子,假设我们有一个谓词a2b/2,有两个参数,第一个

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中的列表,有如下的一些具体例子: [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 翻译 - 第三章 - 递归 - 第三节,练习题和答案

练习题3.1 在之前的章节中,我们已经讨论了如下的谓词逻辑: descend(X, Y) :- child(X, Y). descend(X, Y) :- child(X, Z), descend(Z, Y). 假设我们将谓词逻辑重构如下: descend(X, Y) :- child(X, Y). descend(X, Y) :- descend(X, Z), descend(Z, Y). 这会导致问题吗? 我的答案: 1. 这个谓词逻辑是有问题的,因为规则2中存在左递归的情况,即规则2的主干

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] =