我是prolog的新手,我正在尝试编写一个小程序,给定一个列表,返回列表元素的总和。按照我见过的所有例子,我得到了一个看起来像这样的解决方案:
addup([],0).
addup([FirstNumber | RestOfList], Total) :-
addup(RestOfList, TotalOfRest),
Total is FirstNumber + TotalOfRest.
但是,当我使用这些值测试该解决方案时:
?- addup([1,2,3,4],0).
我只是从_34521获取垃圾值。
然后我尝试了第二个看起来像这样的解决方案:
sum([], 0).
sum([H|T], N):-
X is H+N,
sum(T, X).
这个能够正确地加上数字,但它无法将X的最终值(这是真正的答案)传递给N.所以对于测试用例:
?- sum([1,2,3,4], 0).
我得到6的答案,N的最终值,以及X的倒数第二个值,而不是10,X的最终值。任何帮助和/或解释为什么这些解决方案都不起作用我将不胜感激。感谢。
答案 0 :(得分:2)
首先,空列表的总和为0 - 这是你已经拥有的基本情况。
其次,元素N
和列表H
的总和T
是添加到T
的列表H
的总和。 Prolog运作统一,因此这表示N
与H
和T
的总和统一,其中T
的总和与X
统一使用sum(T,X)
。
sum([], 0).
sum([H|T], N):-
sum(T, X),
N is X + H.
这给出了:
?- sum([1,2,3,4],N).
N = 10.
在您的原始问题中,?- sum([1,2,3,4], 0).
实际上是"如果列表[1,2,3,4]
的总和为0
"则为真。 - 你需要将一个变量作为第二个参数传递给sum
,以便与答案统一。
更多信息:
_34521
表示未绑定的变量 - 这表示存在变量,但尚未与值统一。
次要考虑因素:
对于一个适当长的列表,上面的实现将耗尽堆栈空间,因为必须存储递归的每个帧,以便添加可以自下而上发生。为了防止堆栈错误,我们可以像这样使用尾递归累加器:
sum(L, N):-
sum(L, 0, N).
sum([],N,N).
sum([H|T],A,N) :-
A1 is A + H,
sum(T,A1,N).
...保留原始方法的签名,包装sum / 3。由于规则体中没有选择点(假设A + H对于给定的A和H总是相同的,并且列表是空的或不是),Prolog将丢弃每个帧,因为它留下了范围(因为没有什么可以做的了)并且,在完成时,将返回到原来的呼叫者。
每个实例中sum([1,2,3],N)
的简化堆栈:
非尾递归:
rest-of-stack
rest-of-stack,sum([1|[2,3]],_1001)
rest-of-stack,sum([1|[2,3]],_1001),sum([2|[3]],_1002)
rest-of-stack,sum([1|[2,3]],_1001),sum([2|[3]],_1002),sum([3|[]],_1003)
rest-of-stack,sum([1|[2,3]],_1001),sum([2|[3]],_1002),sum([3|[]],_1003),sum([],0)
rest-of-stack,sum([1|[2,3]],_1001),sum([2|[3]],_1002),sum([3|[]],3)
rest-of-stack,sum([1|[2,3]],_1001),sum([2|[3]],5)
rest-of-stack,sum([1|[2,3]],6)
rest-of-stack
尾递归:
rest-of-stack
rest-of-stack,sum([1,2,3],_1001)
rest-of-stack,sum([1|[2,3]],0,_1001)
rest-of-stack,sum([2|[3]],1,_1001)
rest-of-stack,sum([3|[]],3,_1001)
rest-of-stack,sum([],6,6)
rest-of-stack
请注意,尾递归堆栈对于任何长度的列表都具有有限的深度(取决于调用它的内容),其中非尾递归堆栈的深度与列表的长度成正比。 / p>
因此,N is 10^7,length(L,N),maplist(=(1),L),sum(L,N).
之类的调用(由@false引发)可能会在没有尾递归的情况下失败,并且可能会成功。