我在Prolog中编写这个小代码,用于计算列表中某些术语的出现次数。它有效,但它不是尾递归(所以没有递归优化)。
如何编写相同的程序,但尾部递归?
counter(T,[],X) :-
X is 0.
counter(T,[T|D],X1) :-
!,
counter(T,D,X),
X1 is X+1.
counter(T,[_|D],X1) :-
counter(T,D,X1).
我认为我应该使用累加器,但我不知道如何实现。有什么帮助吗?
答案 0 :(得分:6)
即使在Prolog中也欢迎缩进,以使程序易于理解:
counter(T, [], X) :-
X is 0.
counter(T, [T|D], X1) :-
!,
counter(T, D, X),
X1 is X+1.
counter(T, [_|D], X1) :-
counter(T, D, X1).
正确命名变量也是一种很好的做法,我们可以使用:
counter(Elem, [], Result) :-
Result is 0.
counter(Elem, [Elem|Tail], Result) :-
!,
counter(Elem, Tail, NewResult),
Result is NewResult + 1.
counter(Elem, [_|Tail], Result) :-
counter(Elem, Tail, Result).
给单例变量赋一个特殊名称(用_
作为前缀)也是一个好习惯:
counter(_Elem, [], Result) :-
Result is 0.
counter(Elem, [Elem|Tail], Result) :-
!,
counter(Elem, Tail, NewResult),
Result is NewResult + 1.
counter(Elem, [_Head|Tail], Result) :-
counter(Elem, Tail, Result).
你可以利用这样一个事实,即Prolog在条款的头部使用统一来重写你的第一个条款:
counter(_Elem, [], Result) :-
Result is 0.
可以成为
counter(_Elem, [], 0).
那些仅由头部组成的条款也称为事实
您必须更改的子句是middle子句:递归调用不在谓词的末尾。可悲的是,它也会影响其他条款,让我们明白为什么。
为了得到尾递归,我们使用一个叫做累加器的习语:一个在递归过程中保存中间结果的附加参数。例如:
counter(Elem, List, Result) :-
counter(Elem, List, 0, Result).
counter(_Elem, [], Acc, Acc).
counter(Elem, [Elem|Tail], Acc, Result) :-
!,
NewAcc is Acc + 1,
counter(Elem, Tail, NewAcc, Result).
counter(Elem, [_Head|Tail], Acc, Result) :-
counter(Elem, Tail, Acc, Result).
正如您所看到的,我们现在有一个谓词counter/3
,它只调用counter/4
,而后者跟踪Acc
变量中的中间结果。
您的计划中存在的问题是您使用is/2
。这不会给你一个通用的程序:你不能打电话给counter(X, [1, 2, 3, 4], R)
并得到答案。要纠正您可以使用约束编程:
:- use_module(library(clpfd)).
counter(Elem, List, Result) :-
counter(Elem, List, 0, Result).
counter(_Elem, [], Acc, Acc).
counter(Elem, [Elem|Tail], Acc, Result) :-
NewAcc #= Acc + 1,
counter(Elem, Tail, NewAcc, Result).
counter(Elem, [Head|Tail], Acc, Result) :-
Elem #\= Head,
counter(Elem, Tail, Acc, Result).
测试:
?- counter(X, [1, 2, 3, 4], R).
X = R, R = 1 ;
X = 2,
R = 1 ;
X = 3,
R = 1 ;
X = 4,
R = 1 ;
R = 0,
X in inf..0\/5..sup.
答案 1 :(得分:2)
添加累加器可以使用标准流程完成:
counter( T , List , X ) :-
counter( T , List , 0 , X ) .
counter( _ , [] , Acc , Acc ) .
counter( T , [T|D] , SoFar , Result ) :-
Updated is SoFar+1 ,
! ,
counter(T,D,Updated,Result) .
counter( T , [_|D] , SoFar , Result ) :-
counter( T , D , SoFar , Result ) .
答案 2 :(得分:2)
我们可以根据meta-predicate tcount/3
和明确的术语相等(=)/3
定义counter/3
:
counter(E,Xs,N) :-
tcount(=(E),Xs,N).
样本使用:
?- counter(a,[a,b,a,b,a,c],N). N = 3. ?- counter(E,[a,b,a,b,a,c],N). N = 3, E=a ; N = 2, E=b ; N = 1, E=c ; N = 0, dif(E,a), dif(E,b), dif(E,c).
更一般的查询怎么样?我们得到逻辑上合理的答案吗?
?- counter(X,[A,B,C],2). A=X , B=X , dif(C,X) ; A=X , dif(B,X), C=X ; dif(A,X), B=X , C=X ; false.
是。让我们概括一下上面的查询和 比较解决方案集(并在工作中看到单调性)!
?- counter(X,[A,B,C],N). N = 3, A=X , B=X , C=X ; N = 2, A=X , B=X , dif(C,X) ; N = 2, A=X , dif(B,X), C=X ; N = 1, A=X , dif(B,X), dif(C,X) ; N = 2, dif(A,X), B=X , C=X ; N = 1, dif(A,X), B=X , dif(C,X) ; N = 1, dif(A,X), dif(B,X), C=X ; N = 0, dif(A,X), dif(B,X), dif(C,X).