【问题标题】:Count number of occurrences in a list in Prolog计算 Prolog 列表中出现的次数
【发布时间】:2014-05-06 07:57:46
【问题描述】:

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

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

【问题讨论】:

    标签: prolog tail-recursion


    【解决方案1】:

    程序可读性

    即使在 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).
    

    那些只包含一个头部的子句也称为事实

    尾递归

    你必须改变的子句是中间子句:递归调用不在它的谓词末尾。可悲的是,它也会影响其他条款,让我们看看为什么。

    为了获得尾递归,我们使用了一个称为累加器的习语:一个额外的参数,它将在递归期间保存中间结果。例如这里:

    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.
    

    【讨论】:

    • 用空行分隔不同谓词的子句也是一种很好的做法。在 counter/3 和 counter/4 之间没有分隔会非常混乱。
    • 你能详细解释一下 whi is/2 是一般问题的问题吗?似乎问题在于,如果我们不想让 c 转到 counter/4 的第三个子句,那么剪切 ! 必须删除可行的解决方案。
    【解决方案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 ) .
    

    【讨论】:

      【解决方案3】:

      我们可以根据 tcount/3 和具体化的term-equality (=)/3 来定义counter/3

      counter(E,Xs,N) :- 
         tcount(=(E),Xs,N).
      

      示例用途:

      ?- 计数器(a,[a,b,a,b,a,c],N)。 N = 3。 ?- 计数器(E,[a,b,a,b,a,c],N)。 N = 3, E=a ; N = 2, E=b ; N = 1, E=c ; N = 0,差异(E,a),差异(E,b),差异(E,c)。

      更一般的查询怎么样?我们能得到合乎逻辑的答案吗?

      ?- 计数器(X,[A,B,C],2)。 A=X , B=X , 差异(C,X) ; A=X , 差异 (B,X), C=X ;差异(A,X),B=X,C=X ;错误的。

      是的。让我们概括一下上面的查询和 比较解决方案集(并查看工作中的单调性)!

      ?- 计数器(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,差异(A,X),B=X,差异(C,X) ; N = 1,差异(A,X),差异(B,X),C = X ; N = 0,差异(A,X),差异(B,X),差异(C,X)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多