列表长度在Prolog

时间:2013-10-07 17:02:16

标签: list prolog clpfd

我是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).

5 个答案:

答案 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术语,有两个参数L1,其仿函数为+。 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