【问题标题】:DCG and inversion of a list in PrologDCG和Prolog中列表的反转
【发布时间】:2017-05-20 09:36:50
【问题描述】:

我正在尝试计算列表中 inversions 的数量。谓词inversion(+L,-N)N 统一到该列表中的反转数。 inversion 定义为X > YX 出现在列表中Y 之前(除非XY0)。例如:

?- inversions([1,2,3,4,0,5,6,7,8],N).
N = 0.

?- inversions([1,2,3,0,4,6,8,5,7],N).
N = 3.

对于我使用它的目的,列表将始终包含 9 个元素,并且始终包含唯一的数字 0-8

我对 Prolog 很陌生,我正在尝试尽可能简洁和优雅地做到这一点;看起来DCG 可能会有很大帮助。我阅读了官方定义和一些教程网站,但仍然不明白它是什么。任何帮助将不胜感激。

【问题讨论】:

  • ...而X 在列表中出现在Y 之前...立即 之后,还是之后的任何时候?
  • 这确实是一个不错的 clpfd 示例。我会悬赏 clpfd 解决方案
  • @lurker,之前的任何一点

标签: list prolog clpfd


【解决方案1】:

这是另一个使用if_/3 不留下选择点的解决方案:

inversions([],0).
inversions([H|T], N):-
   if_( H = 0, 
        inversions(T,N),
        ( find_inv(T,H,N1),inversions(T, N2), N #= N1+N2 )
      ).

find_inv([],_,0).
find_inv([H1|T],H,N1):-
   if_( H1=0,
        find_inv(T,H,N1),
        if_( H#>H1, 
             (find_inv(T,H,N2),N1 #= N2+1),
             find_inv(T,H,N1) 
           )
       ).

#>(X, Y, T) :-
   (  integer(X),
      integer(Y)
   -> ( X > Y
      -> T = true
      ;  T = false
      )
   ;  X #> Y,
      T = true
   ;  X #=< Y,
      T = false
   ).

【讨论】:

    【解决方案2】:

    我不太确定 DCG 在这里是否会有所帮助。虽然我们正在处理一个序列,但在查看每个元素时,在每个点上都会对整个列表进行大量检查。

    这是一种 CLPFD 方法,它实现了用于反转的“朴素”算法,因此它是透明且简单的,但没有达到应有的效率(它是 O(n^2))。有一个更有效的算法 (O(n log n)) 涉及 divide and conquer 方法,我将在下面进一步展示。

    :- use_module(library(clpfd)).
    
    inversions(L, C) :-
        L ins 0..9,
        all_distinct(L),
        count_inv(L, C).
    
    % Count inversions    
    count_inv([], 0).
    count_inv([X|T], C) :-
        count_inv(X, T, C1),     % Count inversions for current element
        C #= C1 + C2,            % Add inversion count for the rest of the list
        count_inv(T, C2).        % Count inversions for the rest of the list
    
    count_inv(_, [], 0).
    count_inv(X, [Y|T], C) :-
        (   X #> Y, X #> 0, Y #> 0
        ->  C #= C1 + 1,         % Valid inversion, count it
            count_inv(X, T, C1)
        ;   count_inv(X, T, C)
        ).
    
    ?- inversions([1,2,3,4,0,5,6,7,8],N).
    N = 0 ;
    false.
    
    ?- inversions([1,2,3,0,4,6,8,5,7],N).
    N = 3 ;
    false.
    
    ?-  inversions([0,2,X],1).
    X = 1 ;
    false.
    

    它确实留下了一个选择点,正如你所看到的,我还没有整理出来。


    这是 O(n log n) 解决方案,它使用排序/合并算法。
    inversion([], [], 0).
    inversion([X], [X], 0).
    inversion([HU1, HU2|U], [HS1, HS2|S], C) :- % Ensure list args have at least 2 elements
        split([HU1, HU2|U], L, R),
        inversion(L, SL, C1),
        inversion(R, SR, C2),
        merge(SL, SR, [HS1, HS2|S], C3),
        C #= C1 + C2 + C3.
    
    % Split list into left and right halves
    split(List, Left, Right) :-
        split(List, List, Left, Right).
    split(Es, [], [], Es).
    split(Es, [_], [], Es).
    split([E|Es], [_,_|T], [E|Ls], Right) :-
        split(Es, T, Ls, Right).
    
    % merge( LS, RS, M )
    merge([], RS, RS, 0).
    merge(LS, [], LS, 0).
    merge([L|LS], [R|RS], [L|T], C) :-
        L #=< R,
        merge(LS, [R|RS], T, C).
    merge([L|LS], [R|RS], [R|T], C) :-
        L #> R, R #> 0 #<==> D, C #= C1+D,
        merge([L|LS], RS, T, C1).
    

    您可以忽略第二个参数,它是排序列表(如果您想要的只是反转计数,这只是一个副作用)。

    【讨论】:

    • 貌似很多公开CP。还有((L #&gt; 0, R #&gt; 0) -&gt; ...看起来非常可疑!
    • @false 确实,它展示了 CP。这是一种天真的方法。我确实更新了-&gt; 表达式,因为它有点过分了。
    • @false 好建议。我对#&lt;==&gt;/2 运算符不是很熟悉。
    【解决方案3】:

    这是定义关系的另一种可能性。首先,#&lt;/3#\=/3 可以这样定义:

    :- use_module(library(clpfd)).
    
    bool_t(1,true).
    bool_t(0,false).
    
    #<(X,Y,Truth)  :- X #< Y #<==> B, bool_t(B,Truth).
    #\=(X,Y,Truth)  :- X #\= Y #<==> B, bool_t(B,Truth).
    

    基于此,if_/3(',')/3 可以定义谓词 inv_t/3,根据 OP 给出的定义,在反转的情况下产生 true,否则产生 false:

    inv_t(X,Y,T) :-
       if_(((Y#<X,Y#\=0),X#\=0),T=true,T=false).
    

    随后实际的关系可以这样描述:

    list_inversions(L,I) :-
       list_inversions_(L,I,0).
    
    list_inversions_([],I,I).
    list_inversions_([X|Xs],I,Acc0) :-
       list_x_invs_(Xs,X,I0,0),
       Acc1 #= Acc0+I0,
       list_inversions_(Xs,I,Acc1).
    
    list_x_invs_([],_X,I,I).
    list_x_invs_([Y|Ys],X,I,Acc0) :-
       if_(inv_t(X,Y),Acc1#=Acc0+1,Acc1#=Acc0),
       list_x_invs_(Ys,X,I,Acc1).
    

    因此,OP 给出的示例查询确定性地成功:

    ?- list_inversions([1,2,3,4,0,5,6,7,8],N).
    N = 0.
    
    ?- list_inversions([1,2,3,0,4,6,8,5,7],N).
    N = 3.
    

    【讨论】:

      【解决方案4】:

      这种特定于应用程序的约束通常可以使用reified 约束(其真值反映到 0/1 变量的约束)来构建。这导致了一个相对自然的公式,如果满足您要计算的条件,则 B 为 1:

      :- lib(ic).
      
      inversions(Xs, N) :-
          ( fromto(Xs, [X|Ys], Ys, [_]), foreach(NX,NXs) do
              ( foreach(Y,Ys), param(X), foreach(B,Bs) do
                  B #= (X#\=0 and Y#\=0 and X#>Y)
              ),
              NX #= sum(Bs)       % number of Ys that are smaller than X
          ),
          N #= sum(NXs).
      

      此代码用于ECLiPSe

      【讨论】:

      • inversions(Xs, 1). 失败,但 Xs = [2,1,0], inversions(Xs, 1). 使用 6.2development #21 成功。有更好的版本吗?
      【解决方案5】:

      使用 clpfd et automaton/8 我们可以编写

      :- use_module(library(clpfd)).
      
      inversions(Vs, N) :-
                   Vs ins 0..sup,
                   variables_signature(Vs, Sigs),
                   automaton(Sigs, _, Sigs,
                             [source(s),sink(i),sink(s)],
                             [arc(s,0,s), arc(s,1,s,[C+1]), arc(s,1,i,[C+1]),
                              arc(i,0,i)],
                             [C], [0], [N]),
                  labeling([ff],Vs).
      
      variables_signature([], []).
      
      variables_signature([V|Vs], Sigs) :-
                  variables_signature_(Vs, V, Sigs1),
                  variables_signature(Vs, Sigs2),
                  append(Sigs1, Sigs2, Sigs).
      
      variables_signature_([], _, []).
      
      variables_signature_([0|Vs], Prev, Sigs) :-
            variables_signature_(Vs,Prev,Sigs).
      
      variables_signature_([V|Vs], Prev, [S|Sigs]) :-
            V #\= 0,
            % Prev #=< V #<==> S #= 0,
            % modified after **false** remark 
            Prev #> V #<==> S,
            variables_signature_(Vs,Prev,Sigs).
      

      例子:

      ?- inversions([1,2,3,0,4,6,8,5,7],N).
      N = 3 ;
      false.
      
      ?- inversions([1,2,3,0,4,5,6,7,8],N).
      N = 0 ;
      false.
      
      ?- inversions([0,2,X],1).
      X = 1.
      

      【讨论】:

      • @false : tu es français ?
      • Rendons grâce à Alain Colmerauer !
      • C'est seulment maintenant que j'ai appris la triste nouvelle!
      • 哎哟! Je dois dire que je ne savais rien de ce décès : je plaisantais sur le fait que gràce à Alain Colmerauer, tu avais appris le français pour étudier son Prolog dans sa langue。
      【解决方案6】:

      在 SWI-Prolog 中,带有库 aggregatelists

      inversions(L,N) :-
          aggregate_all(count, (nth1(P,L,X),nth1(Q,L,Y),X\=0,Y\=0,X>Y,P<Q), N).
      

      这两个库都是自动加载的,无需显式包含它们。

      如果您想要更通用的东西,您可以在自动机部分下的 library(clpfd) 中查看示例,以获得一些有用的想法。但我会尝试用更简单的术语重写您的规范,使用element/3 而不是 nth1/3。

      编辑

      在@false 评论之后,我尝试了一些不等式运算符的变体,但我尝试过的没有一个能够解决有问题的查询。然后我再次尝试了最初的想法,以充分利用 element/3。结果如下:

      :- use_module(library(clpfd)).
      
      inversions(L) :-
          L ins 0..8,
          element(P,L,X),
          element(Q,L,Y),
          X #\= 0, Y #\= 0, X #> Y, P #< Q,
          label([P,Q]).
      
      inversions(L,N) :-
          aggregate(count, inversions(L), N) ; N = 0.
      

      最后一行 label([P,Q]) 是正确具体化的关键:现在我们可以确定 X 值了。

      ?- inversions([0,2,X],1).
      X = 1.
      

      【讨论】:

      • 虽然inversions([0,2,1],1). 成功泛化inversions([0,2,X],1). 失败。
      • 你为什么不修复你的程序?就像用=\= 替换\= 或将X&gt;Y 放在第一位一样简单!
      • @false:谢谢,我会的...我曾短暂尝试使用 clpfd:element/3,但没有成功
      • 聚合和 clpfd 不聚集在一起:inversions([0,1,2],N). 现在失败了??
      • 另外,请为单独的解决方案使用单独的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      • 2013-10-28
      • 1970-01-01
      • 2014-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多