计算Prolog中列表中出现的次数

时间:2014-05-06 07:57:46

标签: prolog tail-recursion

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

我认为我应该使用累加器,但我不知道如何实现。有什么帮助吗?

3 个答案:

答案 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)

我们可以根据 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).