Больше детерминизма для `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
, которое позволяет избежать точки выбора, указанной выше?
Ответы
Ответ 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)
...