我正在人工智能实验室学习Prolog,来源Learn Prolog Now!。
在第5章中,我们来了解累积器。作为示例,给出了这两个代码片段。 查找列表的长度
没有累加器:
len([],0).
len([_|T],N) :- len(T,X), N is X+1.
带累加器的:
accLen([_|T],A,L) :- Anew is A+1, accLen(T,Anew,L).
accLen([],A,A).
我无法理解,两个片段在概念上有何不同?累加器到底有什么不同?有什么好处?
累加器听起来像中间变量。 (如果我错了,请纠正我。)到目前为止,我已经在我的程序中使用过它们,所以它真的是一个很大的概念吗?
答案 0 :(得分:11)
当您提供名称时,它会突然变得比以前更加真实。现在可以通过简单地使用概念的名称来讨论某些事情。没有任何哲学,不,没有关于累加器的特殊,但它们是有用的。
实际上,通过没有累加器的列表:
foo([]).
foo([H|T]) :-
foo(T).
列表的头部被遗忘,递归调用无法访问。在每个递归级别,您只能看到列表中剩下的内容。
使用累加器:
bar([], _Acc).
bar([H|T], Acc) :-
bar(T, [H|Acc]).
在每个递归步骤中,您都有剩余的列表和您已经完成的所有元素。在len/3
示例中,您只保留计数,而不是实际元素,因为这就是您所需要的。
某些谓词(如len/3
)可以使用累加器进行尾递归:您不需要等待输入结束(耗尽列表的所有元素)来执行实际工作,而是在获得输入时逐步进行。 Prolog不必在堆栈上保留值,并且可以为您进行尾调用优化。
需要知道“到目前为止的路径”(或任何需要具有状态的算法)的搜索算法使用相同技术的更一般形式,通过向递归调用提供“中间结果”。例如,行程编码器可以定义为:
rle([], []).
rle([First|Rest],Encoded):-
rle_1(Rest, First, 1, Encoded).
rle_1([], Last, N, [Last-N]).
rle_1([H|T], Prev, N, Encoded) :-
( dif(H, Prev)
-> Encoded = [Prev-N|Rest],
rle_1(T, H, 1, Rest)
; succ(N, N1),
rle_1(T, H, N1, Encoded)
).
希望有所帮助。
答案 1 :(得分:10)
TL; DR :是的,他们是。
想象一下,你要从左边的城市 A 到右边的城市 B ,你想提前知道两者之间的距离。你是如何实现这一目标的?
mathematician这样的位置使用magic,称为structural recursion。他对自己说,如果我将自己的副本再向向城市 B 靠近一步,并询问 它到城市的距离?然后我会在其结果中添加1 从我的副本中接收它,因为我已经发送了一个步骤更近走向城市,并且在没有移动一英寸的情况下知道我的答案!当然如果我已经在城门口,我不会发送任何地方的任何副本,因为我知道距离是0 - 没有移动一英寸!
我怎么知道我的 copy-of-me 会成功?仅仅因为他将遵循相同的规则,同时从 close 点开始到我们的目的地。无论我的回答是什么价值,他的将少一个,只有有限数量的我们的副本才会被采取行动 - 因为城市之间的距离是有限的。所以总操作肯定会在有限的时间内完成,我将得到我的答案。因为在无限时间过后得到你的答案,所以永远都不会得到答案。
现在,我们提前找到了他的答案,我们谨慎的魔术师数学家已准备好开始他的安全(现在!)之旅。
但那当然不是魔法 - 这都是一个肮脏的把戏!他没有提前找到答案 - 他已经发出了其他人的整个堆栈来为他找到答案。毕竟,艰苦的工作必须完成,他只是假装没有意识到这一点。距离是旅行的。此外,距离后面也必须走过,每个副本都要将结果告诉他们的主人,结果实际上是在 后面的路上创建的强>来自目的地。所有这一切都在我们的假魔术师开始走路之前。如何 为团队努力。对于他,这似乎是一个甜蜜的交易。但总的来说......
这就是magician mathematician的思考方式。但他的dual勇敢的旅行者只需goes on a journey,并计算他的步骤,在每一步 之前为当前步骤添加1 其余的实际旅程。没有任何借口了。旅程可能是有限的,也可能是无限的 - 他无法预先知道。但是在他的路线上的每个点,因此当他/如果他到达城市 B 门时,他将知道他到目前为止的行进距离。而且他肯定不会一直回到路的尽头告诉自己结果。
这是第一个结构递归与第二个结果递归之间的差异,而第二个采用的是 tail recursion with accumulator / tail recursion modulo cons / corecursion 。第一个知识建立在从目标回来的路上;第二个 - 从 前 开始,朝向目标。
另见:
你问,这一切的实际意义是什么?为什么,想象我们的朋友魔术师数学家需要煮一些鸡蛋。他有一个锅;一个水龙头;热板;和一些鸡蛋。他该怎么办?
嗯,这很容易 - 他只是将鸡蛋放入锅中,从水龙头中加入一些水并将其放在热板上。
如果他已经给了一个装有鸡蛋和水的锅了怎么办?为什么,这对他来说更容易 - 他只是将鸡蛋拿出来,倒出水,最终会遇到他已经知道如何解决的问题!纯粹的魔法,不是吗!
在我们嘲笑这个可怜的家伙之前,we mustn't forget the tale of the centipede。有时无知 幸福。但是当所需的知识很简单并且“一维”如 the distance 时,假装根本就没有记忆是犯罪行为。
答案 2 :(得分:7)
accumulators 是中间变量, 是Prolog中一个重要的(读取基本)主题,因为允许反转一些基本算法的信息流,对该计划的效率产生重要影响。
以反转名单为例
nrev([],[]).
nrev([H|T], R) :- nrev(T, S), append(S, [H], R).
rev(L, R) :- rev(L, [], R).
rev([], R, R).
rev([H|T], C, R) :- rev(T, [H|C], R).
nrev / 2(天真反向)它是O(N ^ 2),其中N是列表长度,而rev / 2则是O(N)。