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

Больше детерминизма для `memberd/2`?

Многие системы обеспечивают чистую и эффективную реализацию member/2. В частности, точка выбора остается открытой для:

?- member(b,[a,b]).
true.

тогда как наивная реализация member/2 дает скорее:

?- member(b,[a,b]).
true ;
false.

что, безусловно, верно с декларативной точки зрения, но менее эффективно.

С другой стороны, существуют некоторые технические проблемы с member/2. Он позволяет использовать избыточные решения, например:

?- member(a,[a,a]).
true ;
true.

memberd/2 решает эту проблему, используя if_/3 и (=)/3.

memberd(E, [X|Xs]) :-
   if_(E = X, true, memberd(E, Xs)).

?- memberd(a,[a,a]).
true.

К сожалению, это определение снова оставляет открытые точки выбора - создание ; false ( "оставшихся точек выбора" ) в ситуациях, когда член не выполняет:

?- memberd(X,[a,b]).
X = a ;
X = b ;
false.    % BAD - to be avoided!

?- member(X,[a,b]).
X = a ;
X = b.

Итак, мой вопрос: существует ли определение memberd/2, которое позволяет избежать точки выбора, указанной выше?

4b9b3361

Ответ 1

Во-первых, для ясности мы переименовываем memberd в memberd_old.

Затем мы реализуем memberd_new/2, который использует индексацию запаздывания и 1-го аргумента, чтобы предотвратить создание бесполезной точки выбора в конце списка.

memberd_new(E,[X|Xs]) :-
   memberd_new_aux(Xs,X,E).

% auxiliary predicate to enable first argument indexing
memberd_new_aux([],E,E).
memberd_new_aux([X1|Xs],X0,E) :-
   if_(E=X0, true, memberd_new_aux(Xs,X1,E)).

Сравним member/2 (встроенный предикат SWI-Prolog), memberd_old/2 и memberd_new/2!

Сначала, наземный запрос:

?- member(a,[a,a]).
true ;
true.                       % BAD!

?- memberd_old(a,[a,a]).
true.

?- memberd_new(a,[a,a]).
true.

Далее, еще один наземный запрос:

?- member(a,[a,b]).
true ;                      % BAD!
false.

?- memberd_old(a,[a,b]).
true.

?- memberd_new(a,[a,b]).
true.

Теперь запрос, содержащий несколько различных решений:

?- member(X,[a,b]).
X = a ;
X = b.

?- memberd_old(X,[a,b]).
X = a ;
X = b ;                     % BAD!
false.

?- memberd_new(X,[a,b]).
X = a ;
X = b.

Изменить

Реализация memberd_new/2, представленная здесь, устарела.

Я рекомендую использовать более новую реализацию, показанную в этом ответе.

Ответ 2

В этом ответе мы сравниваем три разных предиката списка-принадлежности:

  • member/2, встроенный предикат, реализованный в SWI-Prolog.
  • memberd/2, как определено OP:

    memberd(E,[X|Xs]) :-
       if_(E=X, true, memberd(E,Xs)).
    
  • memberd_new/2, предлагаемая альтернатива, которая определяется следующим образом:

    memberd_new(E,[X|Xs]) :-
       (  Xs \= [_|_]
       -> E=X
       ;  if_(E=X, true, memberd_new(E,Xs))
       ).
    

Отпустите!

Во-первых, некоторые наземные запросы:

?- member(b,[a,b]).
true.
?- memberd(b,[a,b]).
true.
?- memberd_new(b,[a,b]).
true.

?- member(a,[a,a]).
true ; true.                        % BAD
?- memberd(a,[a,a]).
true.
?- memberd_new(a,[a,a]).
true.

?- member(a,[a,b]).
true ; false.                       % BAD 
?- memberd(a,[a,b]).
true.
?- memberd_new(a,[a,b]).
true.

Далее, некоторые запросы, имеющие несколько различных решений:

?- member(X,[a,b]).
X = a ; X = b.
?- memberd(X,[a,b]).
X = a ; X = b ; false.              % BAD
?- memberd_new(X,[a,b]).
X = a ; X = b.

Далее, тестовый пример, предложенный в комментарии к предыдущий ответ, который прервал версию memberd_new/2, представленную ранее.

?- member(a,[a|nonlist]).
true.
?- memberd(a,[a|nonlist]).
true.
?- memberd_new(a,[a|nonlist]).
true.                               % IMPROVED

Вариант вышеуказанного тестового случая:

?- member(X,[a|nonlist]).
X = a.
?- memberd(X,[a|nonlist]).
X = a ; false.                      % BAD
?- memberd_new(X,[a|nonlist]).
X = a.                              % IMPROVED

Наконец, некоторые нерелящие запросы:

?- member(1,Xs).
  Xs =          [1|_A]
; Xs =       [_A,1|_B]
; Xs =    [_A,_B,1|_C]
; Xs = [_A,_B,_C,1|_D]
...

?- memberd(1,Xs).
  Xs =          [1|_A]
; Xs =       [_A,1|_B], dif(_A,1)
; Xs =    [_A,_B,1|_C], dif(_A,1), dif(_B,1)
; Xs = [_A,_B,_C,1|_D], dif(_A,1), dif(_B,1), dif(_C,1) 
...

?- memberd_new(1,Xs).
  Xs =          [1|_A]
; Xs =       [_A,1|_B], dif(_A,1)
; Xs =    [_A,_B,1|_C], dif(_A,1), dif(_B,1)
; Xs = [_A,_B,_C,1|_D], dif(_A,1), dif(_B,1), dif(_C,1)
...

Ответ 3

Там больше... Предположим, мы реализуем memberD/2 на основе memberd_t/3:

memberD(X,Xs) :- memberd_t(X,Xs,true).

Как , который сравнивается с memberD/2, как определено OP в вопросе?

Повторите несколько запросов!

?- memberd(a,[a,a]), memberd(a,[a,b]), memberd(b,[a,b]), 
   memberD(a,[a,a]), memberD(a,[a,b]), memberD(b,[a,b]).
true.                             % all of these succeed deterministiaclly

?- memberd(X,[a,b]).
X = a ; X = b ; false.            % could be better
?- memberD(X,[a,b]).
X = a ; X = b ; false.            % could be better

?- memberd(a,[a|nonlist]), memberD(a,[a|nonlist]).
true.                             % both succeed deterministically

?- memberd(X,[a|nonlist]).
X = a ; false.                    % could be better
?- memberD(X,[a|nonlist]).
X = a ; false.                    % could be better

В вышеприведенных запросах memberD/2 и memberD/2 дают одинаковые ответы и оставляют лишние точки выбора в тех же случаях.

Позвольте копать немного глубже! Рассмотрим следующие запросы с помощью memberd_t/3 с угловыми шкафами:

?- memberd_t(a,[a|nonlist],T).
T = true.                         % OK
?- memberd_t(b,[a|nonlist],T).
false.                            % missing: `T = false`
?- memberd_t(X,[a|nonlist],T).
  T = true, X = a 
; false.                          % missing: `T = false, dif(X,a)`

Это не совсем то, чего я ожидал/хотел получить. Что мы можем сделать? В принципе, я вижу два варианта:

  • Примите это несоответствие и провозгласите: "Эти запросы - это угловые случаи, которые не имеют значения".

  • Создайте реализацию memberd_t/3, которая может обрабатывать эти случаи.

Оба варианта имеют сильные и слабые стороны. В дальнейшем мы реализуем memberd_new_t/3, который обрабатывает угловые случаи и уменьшает создание лишних точек выбора.

Предупреждение: уродливый код впереди!

memberd_new_t(E,Xs0,Truth) :-
   (  Xs0 \= [_|_]
   -> Truth = false
   ;  Truth = false,
      freeze(Xs0, Xs0\=[_|_])
   ;  Xs0 = [X|Xs],
      (  Xs \= [_|_]
      -> =(E,X,Truth)
      ;  if_(E=X,Truth=true,memberd_new_t(E,Xs,Truth))
      )
   ).

Позвольте проверить, производим ли мы меньшее количество бесполезных точек выбора с помощью memberd_new_t/3!

?- memberd_t(X,[a,b],true).
X = a ; X = b ; false.              % suboptimal
?- memberd_new_t(X,[a,b],true).
X = a ; X = b.                      % BETTER

?- memberd_t(X,[a|nonlist],true).
X = a ; false.                      % suboptimal
?- memberd_new_t(X,[a|nonlist],true).
X = a.                              % BETTER

Хорошо! Итак, что насчет вышеуказанных угловых случаев?

?- memberd_t(a,[a|nonlist],T).
T = true.                           % OK
?- memberd_new_t(a,[a|nonlist],T).
T = true.                           % OK

?- memberd_t(b,[a|nonlist],T).
false.                              % BAD
?- memberd_new_t(b,[a|nonlist],T).
T = false.                          % OK

?- memberd_t(X,[a|nonlist],T).
  T = true, X = a 
; false.                            % BAD
?- memberd_new_t(X,[a|nonlist],T).
  T =  true,     X=a
; T = false, dif(X,a).              % OK

Это работает! Наконец, рассмотрим наиболее общие запросы:

?- memberd_t(X,Xs,T).          
  T=false, Xs = []            
; T=true , Xs = [X       |_A]        
; T=false, Xs = [_A         ], dif(X,_A)
; T=true , Xs = [_A, X   |_B], dif(X,_A)
; T=false, Xs = [_A,_B      ], dif(X,_A), dif(X,_B)
; T=true , Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, Xs = [_A,_B,_C   ], dif(X,_A), dif(X,_B), dif(X,_C)
...

?- memberd_new_t(X,Xs,T).
  T=false, freeze(Xs,Xs\=[_|_])
; T=true ,                       Xs = [ X      |_A]
; T=false, freeze(_B,_B\=[_|_]), Xs = [_A      |_B], dif(X,_A)
; T=true ,                       Xs = [_A, X   |_B], dif(X,_A)
; T=false, freeze(_C,_C\=[_|_]), Xs = [_A,_B   |_C], dif(X,_A), dif(X,_B)
; T=true ,                       Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, freeze(_D,_D\=[_|_]), Xs = [_A,_B,_C|_D], dif(X,_A), dif(X,_B), dif(X,_C)
...       

Для этих угловых случаев memberd_new_t/3 больше похож на memberd/3 чем memberd_t/3:

?- memberd(X,Xs).
  Xs = [ X            |_A]
; Xs = [_A, X         |_B], dif(X,_A)
; Xs = [_A,_B, X      |_C], dif(X,_A), dif(X,_B)
; Xs = [_A,_B,_C, X   |_D], dif(X,_A), dif(X,_B), dif(X,_C)
; Xs = [_A,_B,_C,_D, X|_E], dif(X,_A), dif(X,_B), dif(X,_C), dif(X,_D)
...