计数出现Prolog

时间:2014-12-17 01:33:34

标签: prolog

我是Prolog的新手并试图用Lists进行一些编程 我想这样做:

?- count_occurrences([a,b,c,a,b,c,d], X).
X = [[d, 1], [c, 2], [b, 2], [a, 2]].

这是我的代码,我知道它不完整,但我正在尝试:

count_occurrences([],[]).
count_occurrences([X|Y],A):-
   occurrences([X|Y],X,N).

occurrences([],_,0).    
occurrences([X|Y],X,N):- occurrences(Y,X,W), N is W + 1.
occurrences([X|Y],Z,N):- occurrences(Y,Z,N), X\=Z.

我的代码错了所以我需要一些点击或帮助PLZ ..

6 个答案:

答案 0 :(得分:3)

请注意,到目前为止,所有提案都存在包含变量的列表的困难。想想这个案子:

?- count_occurrences([a,X], D).

应该有两个不同的答案。

   X = a, D = [a-2] ;
   dif(X, a), D = [a-1,X-1].

第一个答案表示:列表[a,a]包含a两次,因此D = [a-2]。第二个答案涵盖与X不同的所有字词a,对于那些,我们有一次出现a,另一次出现另一个字词。请注意,第二个答案包括无限可能的解决方案,包括X = bX = c或您希望的任何其他解决方案。

如果实现无法产生这些答案,则实例化错误应该保护程序员免受进一步损害。一些事情:

count_occurrences(Xs, D) :-
   ( ground(Xs) -> true ; throw(error(instantiation_error,_)) ),
   ... .

理想情况下,Prolog谓词被定义为纯粹的关系like this one。但通常,纯粹的定义效率很低。

这是一个纯粹而高效的版本。从高效率的角度来看,它不会留下任何不必要的选择点。我把@dasblinkenlight的定义作为灵感来源。

理想情况下,此类定义使用某种形式的if-then-else。但是,传统的(;)/2写了

   ( If_0 -> Then_0 ; Else_0 )

是一种固有的非单调结构。我将使用单调的对手

   if_( If_1, Then_0, Else_0)

代替。主要区别在于条件。传统的控制结构依赖于If_0的成功或失败,它破坏了所有的纯洁。如果你写( X = Y -> Then_0 ; Else_0 ),变量XY是统一的,在那个时间点最终决定是Then_0还是Else_0 。如果变量没有充分实例化,那该怎么办?好吧,那么我们运气不好,并坚持Then_0仅获得一些随机结果。

将此与if_( If_1, Then_0, Else_0)对比。在这里,第一个参数必须是一个目标,它将在其最后一个参数中描述Then_0Else_0是否属实。如果目标尚未确定,则可以选择两者

count_occurrences(Xs, D) :-
   foldl(el_dict, Xs, [], D).

el_dict(K, [], [K-1]).
el_dict(K, [KV0|KVs0], [KV|KVs]) :-
    KV0 = K0-V0,
    if_( K = K0,
         ( KV = K-V1, V1 is V0+1, KVs0 = KVs ),
         ( KV = KV0, el_dict(K, KVs0, KVs ) ) ).

=(X, Y, R) :-
   equal_truth(X, Y, R).

此定义需要以下辅助定义: if_/3equal_truth/3foldl/4

答案 1 :(得分:2)

如果您使用SWI-Prolog,您可以:

:- use_module(library(lambda)).

count_occurrences(L, R) :-
    foldl(\X^Y^Z^(member([X,N], Y)
             ->  N1 is N+1,
             select([X,N], Y, [X,N1], Z)
             ;   Z = [[X,1] | Y]),
          L, [], R).

答案 2 :(得分:1)

应该更容易解决问题的一件事是设计一个辅助谓词来增加计数。

想象一个谓词,它接受一对[SomeAtom,Count]列表和一个需要递增计数的原子,并生成一个具有递增计数的列表,或者对于第一次出现的原子而言[SomeAtom,1] 。这个谓词很容易设计:

increment([], E, [[E,1]]).
increment([[H,C]|T], H, [[H,CplusOne]|T]) :-
    CplusOne is C + 1.
increment([[H,C]|T], E, [[H,C]|R]) :-
    H \= E,
    increment(T, E, R).

当我们添加第一个匹配项时,第一个子句用作基本情况。当head元素与所需元素匹配时,第二个子句用作另一个基本情况。最后一种情况是当head元素与所需元素不匹配时的情况的递归调用。

有了这个谓词,写count_occ变得非常简单:

count_occ([], []).
count_occ([H|T], R) :-
    count_occ(T, Temp),
    increment(Temp, H, R).

这是Prolog的普通递归谓词,带有一个简单的基本子句和一个处理尾部的递归调用,然后使用increment来计算头部元素。列表。

Demo.

答案 3 :(得分:1)

这是我使用bagof/3findall/3的解决方案:

count_occurrences(List, Occ):-
    findall([X,L], (bagof(true,member(X,List),Xs), length(Xs,L)), Occ).

示例

?- count_occurrences([a,b,c,b,e,d,a,b,a], Occ).
Occ = [[a, 3], [b, 3], [c, 1], [d, 1], [e, 1]].

工作原理

列表bagof(true,member(X,List),Xs)的每个不同元素都满足

X,其中Xs是一个列表,其长度等于XList的出现次数}}:

?- bagof(true,member(X,[a,b,c,b,e,d,a,b,a]),Xs).
X = a,
Xs = [true, true, true] ;
X = b,
Xs = [true, true, true] ;
X = c,
Xs = [true] ;
X = d,
Xs = [true] ;
X = e,
Xs = [true].

外部findall/3收集元素X以及代表解决方案的列表中关联列表Xs的长度。

编辑我:得益于CapelliC和Boris的建议,原来的答案得到了改善。

编辑II :如果给定列表中有自由变量,则可以使用setof/3代替findall/3setof/3的问题是,对于空列表,它将失败,因此必须引入特殊子句。

count_occurrences([],[]).
count_occurrences(List, Occ):-
    setof([X,L], Xs^(bagof(a,member(X,List),Xs), length(Xs,L)), Occ).

答案 4 :(得分:1)

你已经得到了答案。 Prolog是一种通常提供多种“正确”方法来解决问题的语言。如果您在答案中坚持任何顺序,那么您的回答并不清楚。因此,忽略顺序,一种方法是:

  1. 使用稳定排序(不会删除重复项)排序列表
  2. 在排序列表中应用行程编码
  3. 这种方法的主要优点是它可以将您的问题解构为两个明确定义(和解决)的子问题。

    第一个很简单:msort(List, Sorted)

    第二个涉及更多,但如果你希望谓词只能以一种方式工作,那么仍然是直接的,即List - >编码。一种可能性(非常明确):

    list_to_rle([], []).
    list_to_rle([X|Xs], RLE) :-
        list_to_rle_1(Xs, [[X, 1]], RLE).
    
    list_to_rle_1([], RLE, RLE).
    list_to_rle_1([X|Xs], [[Y, N]|Rest], RLE) :-
        (    dif(X, Y)
        ->   list_to_rle_1(Xs, [[X, 1],[Y, N]|Rest], RLE)
        ;    succ(N, N1),
             list_to_rle_1(Xs, [[X, N1]|Rest], RLE)
        ).
    

    所以现在,从顶层:

    ?- msort([a,b,c,a,b,c,d], Sorted), list_to_rle(Sorted, RLE).
    Sorted = [a, a, b, b, c, c, d],
    RLE = [[d, 1], [c, 2], [b, 2], [a, 2]].
    

    另一方面,在X-N中选择“对”几乎总是更好,而不是像[X, N]中那样精确地使用两个元素的列表。此外,如果您想要正确,您应该保持列表中元素的原始顺序。来自this answer

    rle([], []).
    rle([First|Rest],Encoded):- 
        rle_1(Rest, First, 1, Encoded).               
    
    rle_1([], Last, N, [Last-N]).
    rle_1([H|T], Prev, N, Encoded) :-
        (   dif(H, Prev) 
        ->  Encoded = [Prev-N|Rest],
            rle_1(T, H, 1, Rest)
        ;   succ(N, N1),
            rle_1(T, H, N1, Encoded)
        ).
    

    为什么会更好?

    • 我们在代码

    • 中删除了4对不必要的括号
    • 我们在报告的解决方案中摆脱了混乱

    • 我们摆脱了大量不必要的嵌套术语:将.(a, .(1, []))-(a, 1)

    • 进行比较
    • 我们让程序的目的更清晰(这是在Prolog中表示对的传统方式)

    从顶层:

    ?- msort([a,b,c,a,b,c,d], Sorted), rle(Sorted, RLE).
    Sorted = [a, a, b, b, c, c, d],
    RLE = [a-2, b-2, c-2, d-1].
    

    所提出的游程编码器在其定义中非常明确,当然它的优点和缺点。请参阅this answer了解更为简洁的方法。

答案 5 :(得分:1)

精炼joel76 answer

count_occurrences(L, R) :-
    foldl(\X^Y^Z^(select([X,N], Y, [X,N1], Z)
             ->  N1 is N+1
             ;   Z = [[X,1] | Y]),
          L, [], R).