List 如何在prolog中获得给定数字的和?

List 如何在prolog中获得给定数字的和?,list,prolog,sum,clpfd,List,Prolog,Sum,Clpfd,我不熟悉prolog,我正在做一些练习。所以我试图得到列表中给定数字的总和。我试着用这个: my_last(X, [X]). my_last(X, [_|L]) :- my_last(X, L). (来自) 作为我的向导。这是我求和的代码: listsum(X, []). listsum(X, [H|L]):- X is H + listsum(X, L). 当我编译它时,它说 practice.pl:3:可评估的列表总和(\u G139,\u

我不熟悉prolog,我正在做一些练习。所以我试图得到列表中给定数字的总和。我试着用这个:

my_last(X, [X]).  
my_last(X, [_|L]) :- my_last(X, L).
(来自)

作为我的向导。这是我求和的代码:

listsum(X, []).                  
listsum(X, [H|L]):-
    X is H + listsum(X, L).
当我编译它时,它说

practice.pl:3:可评估的
列表总和(\u G139,\u G140)
不存在
practice.pl:2:单例变量:
[X]

然后,当我尝试
listsum(0[1,2,3])。
时,它返回
false

我仍然不太了解prolog,以及prolog中的列表和递归。

算术 正如您已经发现的,可以使用
(is)/2
操作符在Prolog中处理算术。这是因为在Prolog中,一切都只是符号演算:默认情况下,事物没有意义,因此统一
(=)/2
不会知道
(+)/2
指的是加法

现在,您的问题是在
(is)/2
(这里是您的递归调用)中使用常规谓词。由于
(is)/2
只执行算术运算,因此它不计算谓词调用。它甚至不能识别它,因为它不是一个算术函数

这里的修复方法是影响递归调用变量的结果,然后在
(is)/2
调用中使用它:

listsum(X,[]).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.
基本情况正确性 但是,如果您测试该代码,您将不会得到期望的结果。原因是你有另一个问题,在你的基本情况。正如您在文中所述,空列表的总和不是“任何东西”

listsum(X,[]).
X
是一个自由变量,因此可以是任意变量)

相反,它是
0

listsum(0, []).
生成的代码是:

listsum(0, []).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.
论据顺序 现在,作为旁注,在Prolog中,一个约定是,输出变量应该放在谓词的末尾,而输入变量应该放在谓词的开头,因此,为了按照需要进行操作,我们可以如下重构:

listsum([], 0).
listsum([Head|Tail], Result) :-
    listsum(Tail, SumOfTail),
    Result is Head + SumOfTail.
尾部调用优化 现在,我们仍然可以使用更先进的技术来改进这个谓词。例如,由于一种称为累加器的声明式编程习惯用法,我们可以引入尾部调用,以便执行尾部调用优化(googlable):

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator is Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).
其背后的思想是在递归的每一步更新一个中间结果(通过向其中添加列表当前头的值),然后声明当列表为空时,这个中间值是最终值

获取更多通用程序 正如您在Prolog中所注意到的,谓词通常可以用几种方式使用。例如,
length/2
可用于发现列表的长度:

?- length([1, 2, 3], Length).
Length = 3.
或使用所需长度的自由变量构建骨架列表:

?- length(List, 3).
List = [_G519, _G522, _G525].
但是,在这里,您可能已经注意到,您不能询问Prolog哪些列表的总和为
6

?- listsum(L, 6).
ERROR: is/2: Arguments are not sufficiently instantiated
这是因为,为了“向后”,当调用
(is)/2
操作符时,Prolog必须求解一个等式。虽然你的算法很简单(只是加法),但在一般情况下,算术是不可解的

为了克服这个问题,可以使用约束编程。一个非常好的图书馆可供SWI、clpfd使用

这里的语法是:

:- use_module(library(clpfd)).

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator #= Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).
现在我们可以用另一种方式使用谓词,我们希望可以使用它:

?- listsum(L, 6).
L = [6] ;
L = [_G1598, _G1601],
_G1598+_G1601#=6 ;
L = [_G1712, _G1715, _G1718],
_G1712+_G1715#=_G1728,
_G1728+_G1718#=6 . % Here I interrupted the answer but it would not terminate.
我们甚至可以要求所有解决问题的方法:

?- listsum(L, X).
L = [],
X = 0 ;
L = [X],
X in inf..sup ;
L = [_G2649, _G2652],
_G2649+_G2652#=X . % Here I interrupted the answer but it would not terminate
我刚才提到了这一点,以便您认识到,通常应该避免使用
(is)/2
,并且最好使用约束编程来获得最通用的程序。

如果可能,请使用而不是普通的
(is)/2
(和朋友)


提供符合您需要的逻辑纯谓词
sum/3

谢谢!成功了!起初我尝试了listsum([1,2,3],0.),但它返回false,然后我尝试了listsum([1,2,3],X)。它显示了总数。但为什么会这样呢?我的意思是,您将其定义为listsum([],0)。但当您在最后一个参数中键入0时,它将返回false。此外,我将阅读有关递归的内容,我不理解其中的递归。因为2个数字就这么简单: