我是Prolog编程的初学者。 我写了这个程序来计算列表的长度。为什么以下程序错了?
length(0, []).
length(L+l, H|T) :- length(L, T).
我写了下面的程序,它工作正常。
length([], 0).
length([H|T], N) :- length(T, N1), N is N1+1.
当我改变订单时,我收到了一个错误。为什么呢?
length([], 0).
length([H|T], N) :- N is N1+1, length(T, N1).
答案 0 :(得分:6)
此代码
length_1(0,[]).
length_1(L+1, [H|T]) :- length_1(L,T).
工作(请注意添加的方括号),但以意想不到的方式。 它将构建一个表达式树,可以在以后进行评估:
?- length_1(X, [1,2,3]), L is X.
X = 0+1+1+1,
L = 3.
在您重写的代码(第二个版本)中,您会收到一个错误,因为在您调用/ 2时N1尚未实例化。
答案 1 :(得分:6)
您需要使用累加器。虽然你可以做这样的事情:
list_length([] , 0 ).
list_length([_|Xs] , L ) :- list_length(Xs,N) , L is N+1 .
将一直递归到列表的末尾,然后,当每次调用返回时,在长度上添加一个,直到它返回到顶级并获得正确的结果。
这种方法的问题是每次递归都会在堆栈上推送一个新的堆栈帧。这意味着如果给出足够长的列表,你将[最终]耗尽堆栈空间。
相反,使用尾递归中介,如下所示:
list_length(Xs,L) :- list_length(Xs,0,L) .
list_length( [] , L , L ) .
list_length( [_|Xs] , T , L ) :-
T1 is T+1 ,
list_length(Xs,T1,L)
.
此代码播种一个带有累加器的worker谓词,以0为种子。在每次递归时,它创建一个新的累加器,其值为当前值+ 1.当到达列表的末尾时,累加器的值为与期望的结果统一。
prolog引擎足够智能(TRO / Tail Recursion Optimization),可以看到它可以在每次调用时重用堆栈帧(因为在递归调用之后没有使用任何本地),从而将递归整齐地转换为迭代。
答案 2 :(得分:4)
正如Carlo的回答所示,L+1
是一个Prolog术语,有两个参数L
和1
,其仿函数为+
。 Prolog不是一种函数式语言,因此您不能键入L+1
并期望将其计算为总和。尝试遵循Carlo的建议并使用is/2
谓词来强制算术评估。例如,调用目标X is 3 + 4
将评估右操作数并尝试使用左操作数统一结果。如果左操作数X
是变量,则它将与7
统一。另请注意,在Prolog中,变量是单个赋值(但如果赋值的术语包含变量,则可以进一步实例化)。即您只能将一个术语分配给变量一次。当您回溯执行任务的目标时,撤消此分配。
答案 3 :(得分:2)
len([], LenResult):-
LenResult is 0.
len([X|Y], LenResult):-
len(Y, L),
LenResult is L + 1.
答案 4 :(得分:0)
其他答案指出了相关问题,但我认为问题仅仅是一个未实例化的变量。在最后一个示例中
length([], 0).
length([H|T], N) :- N is N1+1, length(T, N1).
尽管我们可以在is
的右侧有变量,但必须将它们实例化为整数,以便Prolog可以执行算术运算。在上面的示例中,N1
尚未实例化,并且Prolog无法应用is
函子。问题中未提及错误的类型,但应为instantiation_error
或“参数未充分实例化”(如果在SWI-Prolog中)。
在第二条规则中反转目标时
length([], 0).
length([H|T], N) :- length(T, N1), N is N1+1.
N1
在调用is
时已经被实例化为整数,程序完成而没有错误。
另请参阅this other thread。