Как реализовать предикат not_all_equal/1

Как можно реализовать предикат not_all_equal/1, который будет успешным, если данный список содержит как минимум 2 разных элемента, и потерпит неудачу в противном случае?

Вот моя попытка (не очень чистая):

not_all_equal(L) :-
    (   member(H1, L), member(H2, L), H1 \= H2 -> true
    ;   list_to_set(L, S),
        not_all_equal_(S)
    ).

not_all_equal_([H|T]) :-
    (   member(H1, T), dif(H, H1)
    ;   not_all_equal_(T)
    ).

Это, однако, не всегда имеет лучшее поведение:

?- not_all_equal([A,B,C]), A = a, B = b.
A = a,
B = b ;
A = a,
B = b,
dif(a, C) ;
A = a,
B = b,
dif(b, C) ;
false.

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


person Fatalize    schedule 24.11.2017    source источник
comment
Я бы, наверное, назвал его not_all_same, так как равенство имеет некоторую числовую коннотацию. :)   -  person lurker    schedule 24.11.2017
comment
Связано.   -  person false    schedule 24.11.2017


Ответы (2)


Вот простой способ сделать это и сохранить логическая чистота!

not_all_equal([E|Es]) :-
   some_dif(Es, E).

some_dif([X|Xs], E) :-
   (  dif(X, E)
   ;  X = E, some_dif(Xs, E)
   ).

Вот несколько примеров запросов с использованием SWI-Prolog 7.7.2.

Сначала самый общий запрос:

?- not_all_equal(Es).
   dif(_A,_B), Es = [_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_A,_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_A,_A,_A,_B|_C]
...

Затем запрос, который ОП дал в вопросе:

?- not_all_equal([A,B,C]), A=a, B=b.
   A = a, B = b
;  false.                          % <- the toplevel hints at non-determinism

Наконец, давайте поставим перед собой подцель A=a, B=b:

?- A=a, B=b, not_all_equal([A,B,C]).
   A = a, B = b
;  false.                          % <- (non-deterministic, like above)

Хорошо, но в идеале последний запрос должен был выполниться детерминировано!


Введите library(reif)

Индексирование первого аргумента принимает главный функтор первый предикатный аргумент (плюс несколько простых встроенных тестов) для улучшения детерминизма достаточно конкретизированных целей.

Это само по себе не удовлетворительно покрывает dif/2.

Что мы можем сделать? Работайте с определенным термином "равенство/неравенство" эффективно индексирует dif/2!

some_dif([X|Xs], E) :-                     % some_dif([X|Xs], E) :-
   if_(dif(X,E), true,                     %    (  dif(X,E), true
       (X = E, some_dif(Xs,E))             %    ;  X = E, some_dif(Xs,E)
      ).                                   %    ).

Обратите внимание на сходство новой и старой реализации!

Выше цель X = E избыточна с левой стороны. Давайте удалим!

some_dif([X|Xs], E) :-
   if_(dif(X,E), true, some_dif(Xs,E)).

Отлично! Но, увы, мы еще не совсем закончили (еще)!

?- not_all_equal(Xs).
DOES NOT TERMINATE

Что происходит?

Оказывается, реализация dif/3 не позволяет нам получить красивую последовательность ответов на самый общий запрос. Чтобы сделать это без использования дополнительных целей, форсирующих справедливое перечисление, нам нужна подправленная реализация dif/3, которую я называю diffirst/3:

diffirst(X, Y, T) :-
   (  X == Y -> T = false
   ;  X \= Y -> T = true
   ;  T = true,  dif(X, Y)
   ;  T = false, X = Y
   ).

Давайте использовать diffirst/3 вместо dif/3 в определении предиката some_dif/2:

some_dif([X|Xs], E) :-
   if_(diffirst(X,E), true, some_dif(Xs,E)).

Итак, наконец, вот приведенные выше запросы с новым some_dif/2:

?- not_all_equal(Es).                     % query #1
   dif(_A,_B), Es = [_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_B|_C]
;  dif(_A,_B), Es = [_A,_A,_A,_B|_C]
...

?- not_all_equal([A,B,C]), A=a, B=b.      % query #2
   A = a, B = b
;  false.

?- A=a, B=b, not_all_equal([A,B,C]).      % query #3
A = a, B = b.

Запрос №1 не завершается, но имеет такую ​​же компактную последовательность ответов. Хорошо!

Запрос № 2 по-прежнему не является детерминированным. Хорошо. Для меня это настолько хорошо, насколько это возможно.

Запрос №3 стал детерминированным: Теперь лучше!


Вывод:

  1. Используйте library(reif), чтобы укротить избыточный недетерминизм, сохранив при этом логическую чистоту!
  2. diffirst/3 должен попасть в library(reif) :)



РЕДАКТИРОВАТЬ: более общее использование мета-предикат (предложено комментарием; спасибо!)

Давайте обобщим some_dif/2 так:

:- meta_predicate some(2,?).
some(P_2, [X|Xs]) :-
   if_(call(P_2,X), true, some(P_2,Xs)).

some/2 можно использовать с вещественными предикатами, отличными от diffirst/3.

Вот обновление для not_all_equal/1, которое теперь использует some/2 вместо some_dif/2:

not_all_equal([X|Xs]) :-
   some(diffirst(X), Xs).

Приведенные выше примеры запросов по-прежнему дают те же ответы, поэтому я не буду их здесь показывать.

person repeat    schedule 25.11.2017
comment
По какой-то причине применение diff к первому элементу списка сбивает с толку, но он действительно охватывает все случаи, если подумать об этом... не надо было переосмысливать! - person Fatalize; 26.11.2017
comment
Меня это тоже удивило! Оглядываясь на это сейчас, это совершенно очевидно и является прямым переводом исходной постановки задачи. - person repeat; 26.11.2017
comment
Хорошее и уместное чрезмерное обобщение! Однако это причина, по которой, по вашему мнению, dif/3 следует перечислять ответы по-другому? Я подозреваю, что есть другой предикат, где другой порядок лучше. Чистое совпадение, что перечисление работает с вашим подходом. - person false; 26.11.2017
comment
Как насчет того, чтобы переформулировать some_dif(Es, E)., скажем, как some(Es, dif(E))? - person false; 26.11.2017
comment
... а точнее some(dif(E), Es) - person false; 26.11.2017
comment
@ЛОЖЬ. Мне нравится some/2 с таким порядком аргументов, как maplist/2! Будет редактировать. - person repeat; 26.11.2017
comment
@ЛОЖЬ. Нет, dif/3 только заставил меня задуматься, не будет ли if_/3 еще более привлекательным... - person repeat; 26.11.2017
comment
@ЛОЖЬ. Я также вижу, что это свойство может быть сложно реализовать, например, при использовании not/3. - person repeat; 26.11.2017

Вот частичная реализация с использованием library(reif) для SICStus|SWI. Это, безусловно, правильно, так как выдает ошибку, когда не может продолжить. Но ему не хватает общности, которую мы хотели бы иметь.

not_all_equalp([A,B]) :-
   dif(A,B).
not_all_equalp([A,B,C]) :-
   if_(( dif(A,B) ; dif(A,C) ; dif(B,C) ), true, false ).
not_all_equalp([A,B,C,D]) :-
   if_(( dif(A,B) ; dif(A,C) ; dif(A,D) ; dif(B,C) ; dif(B,D) ), true, false ).
not_all_equalp([_,_,_,_,_|_]) :-
   throw(error(representation_error(reified_disjunction),'C\'est trop !')).

?- not_all_equalp(L).
   L = [_A,_B], dif(_A,_B)
;  L = [_A,_A,_B], dif(_A,_B)
;  L = [_A,_B,_C], dif(_A,_B)
;  L = [_A,_A,_A,_B], dif(_A,_B)
;  L = [_A,_A,_B,_C], dif(_A,_B)
;  L = [_A,_B,_C,_D], dif(_A,_B)
;
! error(representation_error(reified_disjunction),'C\'est trop !')

?- not_all_equalp([A,B,C]), A = a, B = b.
   A = a,
   B = b
;  false.

Редактировать: теперь я понимаю, что мне вообще не нужно добавлять столько dif/2 целей! Достаточно, чтобы одна переменная отличалась от первой! Не нужно взаимной исключительности! Я все еще чувствую себя немного неуверенно, чтобы удалить dif(B,C) цели ...

not_all_equalp([A,B]) :-
   dif(A,B).
not_all_equalp([A,B,C]) :-
   if_(( dif(A,B) ; dif(A,C) ), true, false ).
not_all_equalp([A,B,C,D]) :-
   if_(( dif(A,B) ; dif(A,C) ; dif(A,D) ), true, false ).
not_all_equalp([_,_,_,_,_|_]) :-
   throw(error(representation_error(reified_disjunction),'C\'est trop !')).

Ответы точно такие же... что здесь происходит, думаю я. Является ли эта версия более слабой, менее последовательной?

person false    schedule 24.11.2017
comment
ИМО ?- dif(X,Y,T). действительно выиграет от того же порядка решения, что и ( T = true, dif(X,Y) ; T = false, X=Y ) ! - person repeat; 25.11.2017
comment
На самом деле, это выходит за рамки dif/3; это соглашение о кодировании для материализованных предикатов, которое я хотел бы предложить. Почему? (1) Это дает немного больше контроля library(reif) пользователям интуитивно понятным способом. (2) Подчеркивается сходство с обычной дизъюнкцией Пролога. (3) Последовательность ответов будет такой же. Дополнительные ответы (отсутствующие при использовании (->)/2 или if/3) не будут представлены заранее. Согласно сообщению 101: чтобы прийти к соглашению, сначала подчеркните общее и затем расширите его (а не наоборот)... имеете ли это для вас смысл? Напишу ответ на этот вопрос. - person repeat; 25.11.2017