序言累加器。 他们真的是一个“不同”的概念吗?
我正在学习Prolog下我的人工智能实验室,从源头学习Prolog现在! 。
在第五章中,我们来了解一下累加器 。 作为例子,给出了这两个代码片段。 查找列表的长度
没有蓄电池 :
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).
我无法理解,这两个片段在概念上是不同的? 什么是累加器在做什么不同? 有什么好处?
累加器听起来像中间variables 。 (如果我错了,请纠正我。)到目前为止,我已经在我的程序中使用了它们,那么这真的是一个很大的概念吗?
累加器是中间variables,在Prolog中是一个重要的(读取基本的 )主题,因为它允许颠倒一些基本algorithm的信息stream,对程序的效率有重要的影响。
以倒序列表为例
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)。
当你给一个名字的时候 ,它突然变得比以前更真实了。 现在可以通过简单地使用概念名称来讨论一些事情。 没有更多的哲学,不,没有什么特别的累加器,但它们是有用的。
在实践中,通过没有累加器的列表:
foo([]). foo([H|T]) :- foo(T).
列表的头部留下,不能通过recursion调用访问。 在recursion的每个级别,你只能看到列表中剩下的部分。
使用累加器:
bar([], _Acc). bar([H|T], Acc) :- bar(T, [H|Acc]).
在每一个recursion的步骤中,你都有剩下的列表和你经历的所有元素。 在你的len/3
例子中,你只保留了count,而不是实际的元素,因为这是你所需要的。
有些谓词(如len/3
)可以通过累加器进行尾recursion:您不需要等待input的结束(用尽所有元素)来完成实际的工作,而是像您一样递增得到input。 Prolog不必在堆栈上留下值,并且可以为您做tail-call优化。
searchalgorithm,需要知道“到目前为止的path”(或任何需要有一个状态的algorithm)使用相同技术的更一般forms,通过提供recursion调用的“中间结果”。 例如,游程编码器可以被定义为:
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) ).
希望有所帮助。
TL; DR :是的,他们是。
想象一下,你要从左边的一个城市A去到右边的一个城市B ,并且你想事先知道两者之间的距离。 你怎么做到这一点?
一个math家在这样一个位置使用魔术称为结构recursion 。 他自言自语地说,如果我把自己的副本向 B城靠近一点,问一下它到城市的距离? 我接着从我的副本收到它之后 ,将其结果加1,因为我已经把它向城市靠近了一步,而且不用动一寸就知道我的答案! 当然,如果我已经在城门口了,我不会把任何我的任何副本发送到任何地方,因为我知道距离是0,而不是移动一英寸!
我怎么知道我的副本会成功? 只是因为他会遵循相同的准则,而从接近我们的目的地的一点开始。 无论我的答案是多less, 他的意愿就会less一个,只有有限数量的我们的副本才会被采纳 – 因为城市之间的距离是有限的。 所以总的操作一定会在有限的时间内完成,我会得到我的答案。 因为在无限的时间里得到答案已经过去了,根本就没有得到答案。
而现在,事先已经发现了他的答案,我们谨慎的魔法师math家已经准备好踏上他的安全旅程。
但这当然不是魔术 – 这一切都是一个肮脏的把戏! 他没有提前find答案,他已经发出了一大堆其他人为他find答案。 这件艰苦的工作毕竟要做,他只是假装不知道。 距离已经走了。 此外,还必须走过去的距离,每个副本将结果告诉主人,结果实际上是在从目的地返回的路上创build的。 所有这些在我们的假魔术师之前就已经开始走了。 那对于团队来说如何? 对他来说,这似乎是一个甜蜜的交易。 但总体来说…
这就是魔术师math家所想的。 但是他那双勇敢的旅行者只是走上一段旅程 ,一路走来, 在实际旅程的其余部分之前 ,在每一步的当前台阶上增加1。 没有什么虚饰了 旅程可能是有限的,也可能是无限的 – 他无法预知。 但是在他的路线上的每一点,因此当他/她也到达B城的门口时,他将知道他到目前为止的行程。 而且他肯定不会一路回到起点,告诉自己结果。
这就是第一个结构recursion和第二个采用累加器 / 尾recursion模 / conscursion的 尾recursion之间的区别。 第一个的知识build立在从目标回来的路上; 第二个 – 从出发点出发, 走向目标。
也可以看看:
- 技术报告TR19:将结构recursion迭代为迭代。
Daniel P. Friedman和David S. Wise(1974年12月) 。
这一切的实际含义是什么,你问? 为什么呢,想象一下我们的魔法师math家的朋友需要煮一些鸡蛋。 他有一壶 一个水龙头; 一个热板; 和一些鸡蛋。 他要做什么?
好吧,这很容易 – 他只要把鸡蛋放入锅里,join一些水龙头,然后把它放在热板上。
而如果他已经给它一个鸡蛋和水的锅呢? 为什么,对他来说更容易 – 他只会把鸡蛋拿出来,把水倒掉,最终会遇到他已经知道如何解决的问题! 纯粹的魔力 ,不是吗!
在我们嘲笑这个可怜的小伙子之前, 我们不能忘记 蜈蚣的故事 。 有时候,无知是幸福的。 但是,当所需的知识是简单的,像这里的距离这样的 “一维”时,假装根本没有记忆是一种犯罪。