различный / 2 - существует ли чистое, детерминированное определение?

different(Xs, Ys) :-
   member(X, Xs),
   non_member(X, Ys).
different(Xs, Ys) :-
   member(Y, Ys),
   non_member(Y, Xs).

Хотя это определение с использованием member/2 и non_member/2 почти 1 идеален с декларативной точки зрения, он создает избыточные решения для определенных запросов и оставляет точки выбора повсюду .

Какое определение улучшает это (в чистом виде, вероятно, используя if_/3 и (=)/3) таким образом, что точно такой же набор решений описывается different/2, но является определяющим, по крайней мере, для наземных запросов (таким образом, не оставляет открытыми бесполезные точки выбора) и пропускает (если возможно) лишний ответ?


1 На самом деле different([a|nonlist],[]), different([],[b|nonlist]) успешно. Он также мог потерпеть неудачу. Так что решение, которое не подходит для обоих, подойдет (может быть, даже лучше).


person false    schedule 16.06.2015    source источник
comment
Мы говорим о списках или наборах, потому что это может иметь некоторые последствия (особенно в отношении эффективности).   -  person Willem Van Onsem    schedule 16.06.2015
comment
@CommuSoft: Я избегал упоминания этих тесно связанных понятий полностью, чтобы лучше сосредоточиться на реальном определении. Конечно, намерение состояло в том, чтобы представить множества, но это знание ничего не должно изменить. В любом случае, дубликаты возможны!   -  person false    schedule 16.06.2015
comment
Кроме того, этот предикат, кажется, слишком много работает: при запросе с different([a,b],Y). он дает: Y = [_G122], dif(_G122, a) ;, но dif(_G122,a); не требуется: даже если он равен a, это не проблема. Конечно, если кто-то запросит different([a,b],[Y]), он получит dif(Y,a), dif(Y,b) и dif(Y,b),dif(Y,a), но все же это не обязательно.   -  person Willem Van Onsem    schedule 16.06.2015
comment
@CommuSoft: До сих пор я вообще не думал об избыточности на этом уровне. Меня полностью увлекла неэффективность наземных запросов. Так что, если вам удастся найти чистое определение, учитывающее это, будет даже лучше! Наград много :-)   -  person false    schedule 16.06.2015


Ответы (8)


Давайте доведем его до предела --- с помощью list_nonmember_t/3, _ 2_ и or_/2!

some_absent_t(Xs,Ys,Truth) :-
   exists_in_t(list_nonmember_t(Ys),Xs,Truth).

different(Xs,Ys) :-
   or_(some_absent_t(Xs,Ys),
       some_absent_t(Ys,Xs)).
person repeat    schedule 24.07.2015

Первая попытка!

Следующий код основан на мета-предикатах tfilter/3 и _ 2_, монотонная управляющая конструкция« если-то-еще », if_/3, овеществленная унарная логическая связка not_t/3 и обобщенный термин предикат равенства _ 5_:

different([],[_|_]).
different([X|Xs0],Ys0) :-
   tpartition(=(X),Ys0,Es,Ys1),
   if_(Es=[], true, (tfilter(not_t(=(X)),Xs0,Xs1),different(Xs1,Ys1))).

Пример запроса:

?- different([A,B],[X,Y]).
                A=Y ,           dif(B,Y),     X=Y
;     A=X           ,     B=X           , dif(X,Y)
;     A=X           , dif(B,X), dif(B,Y), dif(X,Y)
;               A=Y ,               B=Y , dif(X,Y)
;               A=Y , dif(B,X), dif(B,Y), dif(X,Y)
; dif(A,X), dif(A,Y).

Соблюдаем детерминизм при работе с наземными данными:

?- different([5,4],[1,2]).
true.

Вышеупомянутый подход кажется шагом в правильном направлении ... Но, как есть, я бы не назвал его идеальным.

person repeat    schedule 16.06.2015
comment
Вы имеете в виду not_t/2, не так ли? - person false; 16.06.2015
comment
Является ли это определяющим для основного случая? - person false; 16.06.2015
comment
@ложный. Я имел в виду not_t(Goal,Param,Truth1) :- call(Goal,Param,Truth0), truth_negated(Truth0,Truth1). с truth_negated(true,false). truth_negated(false,true). IMO, третий параметр требуется для потоковой передачи через проверяемый элемент. - person repeat; 16.06.2015
comment
@ложный. Любые подсказки / подсказки о том, как систематически проверять детерминизм? (возможно, используя интерфейс setup_call_cleanup/3 ...) - person repeat; 16.06.2015
comment
det(G_0) :- setup_call_cleanup(true,G_0, Det=true), ( Det\==true -> throw(...) ; true). Но лучше всего было бы использовать красивый, запоминающийся префикс op, который вписывается в эти операции по отладке - person false; 16.06.2015

Вот еще одна попытка! Мы используем монотонную управляющую конструкцию if-then-else if_/3 в сочетании с предикатом членства в реифицируемом списке memberd_t/3 и индексация первого аргумента, чтобы избежать создания бесполезных точек выбора.

different(Xs,Ys) :-
   different_aux(Xs,Ys,Xs,Ys).

different_aux([],[_|_],Xs0,Ys0) :-
   different_aux(Ys0,[],Ys0,Xs0).     % swap Xs/Ys pair
different_aux([X|Xs],Ys,Xs0,Ys0) :-
   if_(memberd_t(X,Ys0),
       different_aux(Ys,Xs,Ys0,Xs0),  % variant: different_aux(Xs,Ys,Xs0,Ys0)
       true).

Сначала мы выполняем запрос, который ожидаем неудачи:

?- different([1,2,3],[2,3,1]).
false.

Следующие запросы похожи на приведенный выше ошибочный запрос; в каждом из них есть один «другой» элемент x, помещенный в разные индексы в первом [1,2,3] или втором списке [2,3,1]:

?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
   different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
   different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true.                                 % all subgoals succeed deterministically

Хорошо! Давайте запустим еще один (довольно общий) запрос, который я использовал в моем предыдущем ответе:

?- different([A,B],[X,Y]).
      A=X ,               B=X , dif(Y,X)
;     A=X ,           dif(B,X), dif(Y,B)
;               A=Y , dif(B,X), dif(Y,X)
; dif(A,X), dif(A,Y).

Компактность! Большое улучшение по сравнению с тем, что я представил ранее!

person repeat    schedule 30.06.2015
comment
Гипотеза: different/2 (как указано OP) относится к different/2 (показано выше), как CNF относится к DNF. (Ну, на самом деле не совсем определение, данное выше, но вариант, который не меняет местами X и Y на каждом шаге --- как печально известный split/3 --- но делает это только один раз, когда первый аргумент - пустой список.) - person repeat; 30.06.2015
comment
Ультра-нит: верхний уровень SWI различает последний определенный ответ и прерванный запрос путем добавления одного пробела. Рассмотрим запрос true;false., который выдает true . в качестве ответа, если вы прервете запрос. Таким образом: добавление пробелов перед . - конечным знаком (* 6.4.8 *) намекает, что ответ не был определен. - person false; 30.06.2015
comment
Лучше. Но раскрывает ли это намерение? - person false; 02.07.2015
comment
@ложный. В некотором смысле вариация different_aux(Xs,Ys,Xs0,Ys0) есть. Я вижу это так: два предложения different/2 (предоставленные OP) связаны с логическим или и могут вызывать избыточные решения. different_aux Связь OTOH с логическим и. Это делает необходимым сохранение Xs и Ys (поскольку функция обратного отслеживания Пролога больше этого не делает). - person repeat; 02.07.2015

(Во многом вдохновленный последним ответом @ repeat, имена все еще слишком неуклюжие)

different(Xs, Ys) :-
   if_(tnotexists_inlist_t(list_memberd_t(Ys), Xs),
      true,
      tnotexists_inlist_t(list_memberd_t(Xs), Ys)).

tnotexists_inlist_t(_P_2, [], false).
tnotexists_inlist_t(P_2, [E|Es], T) :-
   if_(call(P_2, E),
      tnotexists_inlist_t(P_2, Es, T),
      T = true).
person false    schedule 08.07.2015
comment
Интересно, должно ли имя предиката включать list (например, maplist) или эта информация фактически становится избыточной за счет структурирования кода в модулях. - person repeat; 09.07.2015

Вернуться к истокам! Этот вариант очень близок к коду, указанному OP в вопросе.

Следующее основано на if_/3 и memberd_t/3.

different(Xs,Ys) :-
   if_(some_absent_t(Xs,Ys),
       true,
       some_absent_t(Ys,Xs,true)).

some_absent_t([]    ,_ ,false).
some_absent_t([X|Xs],Ys,Truth) :-
   if_(memberd_t(X,Ys), some_absent_t(Xs,Ys,Truth), Truth=true).

Вот наземный запрос:

?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
   different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
   different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true.                                 % all subgoals succeed deterministically

И вот (более общий) запрос, который я использовал в предыдущих ответах:

?- different([A,B],[X,Y]).
      A=X ,               B=X ,           dif(Y,X)
;     A=X ,           dif(B,X), dif(B,Y)
;               A=Y ,               B=Y , dif(Y,X), dif(Y,X)
;               A=Y , dif(B,X), dif(B,Y), dif(Y,X)
; dif(A,X), dif(A,Y).
person repeat    schedule 07.07.2015
comment
Имя _aux не очень показательно. Нет лучшего имени? - person false; 08.07.2015
comment
different_aux_t(Xs, Ys, T): существует X в Xs такой, что: X notin Ys. - person false; 08.07.2015

Следующая участница конкурса красоты! -)

В этом ответе показан измененный вариант кода, показанный в предыдущем ответе. Он использует овеществленное соединение и дизъюнкцию:

and_(P_1,Q_1) :-
   and_t(P_1,Q_1,true).

or_(P_1,Q_1) :-
   or_t(P_1,Q_1,true).

and_t(P_1,Q_1,Truth) :-
   if_(P_1, call(Q_1,Truth), Truth=false).

or_t(P_1,Q_1,Truth) :-
   if_(P_1, Truth=true, call(Q_1,Truth)).

Обратите внимание на две версии для «и» и «или»; те, у которых есть суффикс _t, имеют дополнительный аргумент для значения истинности, те, у которых нет суффикса, нет и предполагают, что Truth=true должно выполняться.

На основе and_t/3 и предиката неравенства обобщенных терминов dif/3 мы определяем nonmember_t/3:

nonmember_t(X,Ys,Truth) :-
   list_nonmember_t(Ys,X,Truth).

list_nonmember_t([]    ,_, true).
list_nonmember_t([Y|Ys],X,Truth) :-
   and_t(dif(X,Y), list_nonmember_t(Ys,X), Truth).

Теперь давайте определим some_absent_t/3, different_t/3 и different/2, вот так:

some_absent_t([]    ,_ ,false).
some_absent_t([X|Xs],Ys,Truth) :-
   or_t(nonmember_t(X,Ys), some_absent_t(Xs,Ys), Truth).

different_t(Xs,Ys,Truth) :-
   or_t(some_absent_t(Xs,Ys),
        some_absent_t(Ys,Xs),
        Truth).

different(Xs,Ys) :-
   different_t(Xs,Ys,true).

Он все еще работает?

?- different([A,B],[X,Y]).
      A=X ,               B=X ,           dif(Y,X)
;     A=X ,           dif(B,X), dif(B,Y)
;               A=Y ,               B=Y , dif(Y,X), dif(Y,X)
;               A=Y , dif(B,X), dif(B,Y), dif(Y,X)
; dif(A,X), dif(A,Y).                     % same result as before

?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
   different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
   different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true.                                    % same result as before

Выглядит хорошо!

В целом, это не огромное улучшение по сравнению с существующими ответами, но IMO несколько более читаемый код и обновленная версия different/2 в качестве дополнительного бонуса!

person repeat    schedule 09.07.2015
comment
@ложный. До сих пор я использовал и or_t/3, и or_/2. Я понимаю, что or_t/3 должно стать лучше (;)/3 (то же самое для and_t/3 и ','/3). А как насчет or_/2 и and_/2? Это специализации if_/3, но их просто нельзя переименовать в (;)/2 и ','/2. - person repeat; 20.10.2015
comment
@ложный. Я вижу if_/3, как если бы он был определен if_(Cond,Then,Else) :- if_t(Cond,Then,Else,true)., хотя указанный if_t/4 не использовался (пока). Тем не менее, у нас должна быть понятная и понятная схема, чтобы if_/3 пользователи могли без особых проблем предлагать две версии предиката, ИМО. - person repeat; 20.10.2015

Не так давно была предложена следующая жирным шрифтом (+500):

Идиоматический ответ здесь все еще отсутствует. Например, or_t / 3 должно быть (;) / 3. Это еще не все.

Вызов принят! Этот ответ является продолжением предыдущего ответа.

  1. Мы используем овеществленную логическую связку (;)/3, которую можно определить так:

    ';'(P_1,Q_1,T) :- if_(P_1, T=true, call(Q_1,T)).
    
  2. Затем мы определяем мета-предикат call_/1. Это полезно с овеществленными предикатами, используемыми в этом ответе. По своему названию и семантике call_/1 следует за if_/3, and_/2 и or_/2!

    call_(P_1) :- call(P_1,true).
    
  3. Используя (;)/3, call_/1 и some_absent_t/3, мы реализуем different/2:

    different(As,Bs) :- call_((some_absent_t(As,Bs) ; some_absent_t(Bs,As))).
    

Готово! Вот и все.


Давайте повторно запустим запросы, которые мы использовали в предыдущих ответах!

?- different([5,4],[1,2]).
true.

?- different([1,2,3],[2,3,1]).
false.

?- different([4,2,3],[2,3,1]), different([1,4,3],[2,3,1]), different([1,2,4],[2,3,1]),
   different([1,2,3],[4,3,1]), different([1,2,3],[2,4,1]), different([1,2,3],[2,3,4]).
true.

Те же вопросы, те же ответы ... Мне кажется, хорошо!

person repeat    schedule 23.10.2015

Что касается решений, в которых используется if_, я бы сказал, что альтернативным подходом было бы использование конструктивного отрицания с самого начала. Конструктивное отрицание исследовалось еще в 80-х годах. , пионером был Дэвид Чан, и до сих пор время от времени всплывает.

Предположим, у нас есть интерпретатор Пролога, который имеет конструктивное отрицание (~)/2. Это конструктивное отрицание (~)/2 можно использовать для определения декларативного if-then-else следующим образом, позвольте вызвать этот оператор (~->)/2:

  (A ~-> B; C) :- (A, B); (~A, C).

Если интерпретатор Пролога также имеет встроенную импликацию (=>)/2 помимо конструктивного отрицания, можно в качестве альтернативы определить декларативное if-then-else следующим образом, позвольте вызвать этот оператор (~=>)/2:

  (A ~=> B; C) :- (A => B), (~A => C).

Обратите внимание на переход от дизъюнкции (;)/2 к соединению (,)/2. Спросите людей, использующих бинарную диаграмму решений (BDD), почему они логически эквивалентны. Процедурно есть нюансы, но через черный ход встроенной импликации для определенного A второй вариант if-then-else также привнесет недетерминированность.

Но как бы мы могли обойтись и ввести, например, конструктивное отрицание в интерпретаторе Пролога? Самый простой способ - делегировать конструктивное отрицание решателю ограничений. Если решатель ограничений реализовал отрицание, это можно сделать следующим образом:

 ~ A :- #\ A.

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

Также обратите внимание, что обычные подходы к конструктивному отрицанию также предполагают, что импликация обычного правила получает другое прочтение. Это означает, что обычно при конструктивном отрицании мы могли бы выбрать обычное определение member/2 и получить такие результаты запроса, как:

?- ~ member(X, [a,b,c]).
dif(X, a),
dif(X, b),
dif(X, c).

Но опять же, овеществленное отрицание вряд ли будет легко работать с определенными предикатами, поэтому следующий запрос, вероятно, не будет работать:

?- #\ member(X, [a,b,c]).
/* typically won't work */

Если вышеперечисленное завершится успешно, то любой из декларативных if-then-else, таких как (~->)/2 или (~=>)/2, будет реже использоваться, поскольку обычные определения предикатов уже доставляют декларативную интерпретацию интерпретатором Пролога. Но почему конструктивное отрицание не имеет широкого распространения? Одной из причин может быть большое пространство для дизайна такого интерпретатора Пролога. Я заметил, что у нас будут следующие варианты:

Обратная цепочка. Мы бы разделили обычный интерпретатор solve/1 на два предиката solvep/1 и solven/1. solvep/1 отвечает за решение положительных целей, а solven/1 отвечает за решение отрицательных целей. Когда мы попробуем это сделать, мы рано или поздно увидим, что нам нужно более внимательно относиться к кванторам и, вероятно, в конечном итоге получим метод исключения кванторов для области Herbrand.

Прямая цепочка. Мы также заметим, что при обратной цепочке мы столкнемся с проблемами при моделировании предложений с дизъюнкцией или экзистенциальным квантификатором в голове. Это связано с тем, что исчисление секвенций, подходящее для Пролога, имеет только одну формулу в правой части. Таким образом, либо мы перейдем к нескольким формулам с правой стороны и потеряем параконсистентность, либо мы будем использовать прямую цепочку.

Волшебные наборы: но прямая цепочка неконтролируемым образом загрязняет пространства решений. Так что нам может понадобиться какая-то комбинация прямого и обратного связывания. Я не имею в виду только то, что мы должны иметь возможность динамически переключаться между ними во время процесса решения, но я имею в виду, что нам также необходимы средства для создания наборов, которые направляют процесс прямой цепочки.

Дополнительные проблемы также отмечены в этом ответе здесь. Это не означает, что рано или поздно будет найдена формула, которая объединит все пары проблемы и решения, но это может занять некоторое время.

Пока

person Mostowski Collapse    schedule 01.11.2015
comment
Разница здесь в накладных расходах: if_/3 - добавлено одно правило. Больше ничего. И во многих случаях производительность сопоставима с нечистым и эффективным кодом. Конечно, можно добавить конструктивное отрицание, например: cn_t(G_0, true) :- G_0. cn_t(G_0, false) :- ~ G_0. Но, как и в вашем определении (~->)/2, нет прямого способа избежать бесполезных точек выбора. - person false; 01.11.2015
comment
Предположим, вы можете сделать все возможное, тогда у вас все еще есть (;)/2! что создает бесполезную точку выбора. Однако if_/3 можно тривиально расширить. (На самом деле, чтобы быть 100% ISO, это не так тривиально, но близко). - person false; 01.11.2015