Подтвердить что ты не робот

DCG и инверсия списка в Prolog

Я пытаюсь подсчитать количество инверсий в списке. Предикат inversion(+L,-N) объединяет N с числом инверсий в этом списке. Инверсия определяется как X > Y и X появляется перед Y в списке (если X или Y не является 0). Например:

?- 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, вероятно, многое поможет. Я читал официальное определение и некоторые учебные сайты, но до сих пор не понимаю, что это такое. Любая помощь будет принята с благодарностью.

4b9b3361

Ответ 1

Такие специфические для приложения ограничения часто могут быть построены с использованием ограниченных ограничений (ограничения, значение истинности которых отражается в переменной 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.

Ответ 2

Я не уверен, что DCG будет здесь полезен. Несмотря на то, что мы обрабатываем последовательность, при каждом просмотре каждого элемента просматривается весь список всего списка.

Здесь используется CLPFD-подход, который реализует "наивный" алгоритм для инверсий, поэтому он прозрачен и прост, но не настолько эффективен, насколько это возможно (это O (n ^ 2)). Там более эффективный алгоритм (O (n log n)), включающий подход к разделению и завоеванию, который я покажу ниже.

:- 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), использующее алгоритм sort/merge.
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).

Вы можете игнорировать второй аргумент, который является отсортированным списком (только побочный эффект, если все, что вы хотите, это количество инверсий).

Ответ 3

Вот еще одно решение, которое не оставляет точек выбора с помощью 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
   ).

Ответ 4

Вот еще одна возможность определить отношение. Во-первых, #</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 может быть определено, что дает true в случае инверсии и false в противном случае, согласно определению, данному OP:

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.

Ответ 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.

Ответ 6

в SWI-Prolog, с библиотеками aggregate и lists:

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

обе библиотеки автоматически загружаются, не нужно явно их включать.

Если вам нужно что-то более общее, вы можете увидеть пример в библиотеке (clpfd), в разделе автомата, для некоторых полезные идеи. Но я попробую переписать вашу спецификацию в более простых выражениях, используя element/3 вместо nth1/3.

изменить

после комментария @false, я пробовал некоторые изменения в операторах несоответствия, но ни один из тех, которые я пробовал, не смог решить проблемный запрос. Затем я снова попытался с оригинальной идеей, чтобы использовать хороший элемент /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.