Prolog 计数出现次数序言

Prolog 计数出现次数序言,prolog,Prolog,我是Prolog新手,正在尝试使用列表进行编程 我想这样做: ?- 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). occurre

我是Prolog新手,正在尝试使用列表进行编程
我想这样做:

?- 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。

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

想象一个谓词,它接受一个成对的列表
[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\u occ
变得非常简单:

count_occ([], []).
count_occ([H|T], R) :-
    count_occ(T, Temp),
    increment(Temp, H, R).
这是Prolog的常规递归谓词,带有一个普通的base子句和一个递归调用,该调用处理尾部,然后使用
increment
来解释列表的head元素


这是我使用
bagof/3
findall/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,成员(X,List),Xs)
满足列表
X
的每个不同元素,其中
Xs
是一个长度等于
X
列表中出现次数的列表:

?- 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

编辑一:多亏了Capelical和Boris的建议,原来的答案得到了改进

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

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

如果使用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).

你已经得到了答案。Prolog是一种语言,它通常提供多种“正确”的方法来处理问题。从你的回答中不清楚你是否坚持回答中的任何顺序。因此,忽略顺序,一种方法是:

  • 使用稳定排序(不删除重复项的排序)对列表进行排序
  • 对排序列表应用运行长度编码
  • 这种方法的主要优点是,它将您的问题分解为两个定义良好(并已解决)的子问题

    第一个很简单:
    msort(列表,排序)

    第二个更复杂,但如果希望谓词只以一种方式工作,即列表-->编码,则仍然是直接的。一种可能性(非常明确):

    所以现在,从最高层:

    ?- 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]].
    
    ?- 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].
    
    另一方面,最好选择“配对”,如
    X-N
    ,而不是像
    [X,N]
    那样只列出两个元素。此外,如果希望正确,您应该保持列表中元素的原始顺序。发件人:

    为什么更好

    • 我们在代码中去掉了4对不必要的括号

    • 我们在报告的解决方案中消除了混乱

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

    • 我们让读者更清楚地了解了程序的意图(这是在Prolog中表示成对的传统方法)

    从最高层:

    ?- 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]].
    
    ?- 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].
    
    所提出的游程编码器的定义非常明确,这当然有其优点和缺点。请参阅,以获取更简洁的操作方法。

    精炼joel76:


    请注意,到目前为止,所有提案都难以处理同样包含变量的列表。想想这个例子:

    ?- count_occurrences([a,X], D).
    
    应该有两种不同的答案

       X = a, D = [a-2] ;
       dif(X, a), D = [a-1,X-1].
    
    第一个答案的意思是:列表
    [a,a]
    包含
    a
    两次,因此
    D=[a-2]
    。第二个答案涵盖了与
    a
    不同的所有术语
    X
    ,对于这些术语,我们有一次出现
    a
    ,另一次出现该术语。请注意,第二个答案包括无限多可能的解决方案,包括
    X=b
    X=c
    或您希望的任何其他解决方案

    如果一个实现不能产生这些答案,实例化错误应该可以保护程序员免受进一步的损害。一些事情:

    count_occurrences(Xs, D) :-
       ( ground(Xs) -> true ; throw(error(instantiation_error,_)) ),
       ... .
    
    理想情况下,Prolog谓词定义为纯关系。但通常,纯粹的定义效率很低

    这是一个纯粹而高效的版本。有效的意义在于它不会留下任何不必要的选择点。我把@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)
    变量
    X
    Y
    是统一的,在这个时候,最终决定是选择
    Then_0
    还是
    Else_0
    。如果变量没有得到充分的实例化,会发生什么?那么,我们运气不好,只坚持
    then\u 0
    ,就会得到一些随机结果

    将此与
    if\u1(if\u1,Then\u0,Else\u0)
    进行对比。这里,第一个参数必须是某个目标,它将在最后一个参数中描述
    Then\u 0
    Else\u 0
    是否是这种情况。目标应该是什么
       if_( If_1, Then_0, Else_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).