Prolog - 如何获得列表元素的总和?

时间:2017-02-07 16:13:09

标签: list prolog

我是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的最终值。任何帮助和/或解释为什么这些解决方案都不起作用我将不胜感激。感谢。

1 个答案:

答案 0 :(得分:2)

首先,空列表的总和为0 - 这是你已经拥有的基本情况。

其次,元素N和列表H的总和T是添加到T的列表H的总和。 Prolog运作统一,因此这表示NHT的总和统一,其中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引发)可能会在没有尾递归的情况下失败,并且可能会成功。