Ответ 1
Ну, во-первых, имя должно быть более декларативным, например equality_truth/2
.
Чистые программы Пролога, которые различают равенство и неравенство терминов в чистом виде, страдают от неэффективности исполнения; даже когда все условия релевантности обоснованы.
Недавним примером SO является этот ответ. Все ответы и все сбои верны в этом определении. Рассмотрим:
?- Es = [E1,E2], occurrences(E, Es, Fs).
Es = Fs, Fs = [E, E],
E1 = E2, E2 = E ;
Es = [E, E2],
E1 = E,
Fs = [E],
dif(E, E2) ;
Es = [E1, E],
E2 = E,
Fs = [E],
dif(E, E1) ;
Es = [E1, E2],
Fs = [],
dif(E, E1),
dif(E, E2).
В то время как программа безупречна с декларативной точки зрения, ее прямое выполнение в текущих системах, таких как B, SICStus, SWI, YAP, излишне неэффективно. Для следующей цели точка выбора остается открытой для каждого элемента в списке.
?- occurrences(a,[a,a,a,a,a],M). M = [a, a, a, a, a] ; false.
Это можно наблюдать, используя достаточно большой список a
следующим образом. Возможно, вам придется адаптировать I
так, чтобы список мог быть представлен; в SWI это означало бы, что
1mo I
должен быть достаточно мал, чтобы предотвратить ошибку ресурса для глобального стека, как показано ниже:
?- 24=I,N is 2^I,length(L,N), maplist(=(a),L). ERROR: Out of global stack
2do I
должен быть достаточно большим, чтобы вызвать ошибку ресурса для локального стека:
?- 22=I,N is 2^I,length(L,N), maplist(=(a),L), ( Length=ok ; occurrences(a,L,M) ). I = 22, N = 4194304, L = [a, a, a, a, a, a, a, a, a|...], Length = ok ; ERROR: Out of local stack
Чтобы преодолеть эту проблему и по-прежнему сохранять хорошие декларативные свойства, необходим предикат сравнения.
Вот такое возможное определение:
equality_reified(X, X, true). equality_reified(X, Y, false) :- dif(X, Y).
Изменить: Возможно, порядок аргументов должен быть отменен, как и ISO, встроенный compare/3
(ссылка только на черновик).
Эффективная реализация его сначала обрабатывала бы быстро определенные случаи:
equality_reified(X, Y, R) :- X == Y, !, R = true. equality_reified(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different equality_reified(X, Y, R) :- X \= Y, !, R = false. % semantically different equality_reified(X, X, true). equality_reified(X, Y, false) :- dif(X, Y).
Изменить: мне непонятно, подходит ли X \= Y
защитник при наличии ограничений. Без ограничений ?=(X, Y)
или X \= Y
совпадают.
Как показано в [user1638891], вот пример того, как можно использовать такой примитив. Исходный код матов был:
occurrences_mats(_, [], []).
occurrences_mats(X, [X|Ls], [X|Rest]) :-
occurrences_mats(X, Ls, Rest).
occurrences_mats(X, [L|Ls], Rest) :-
dif(X, L),
occurrences_mats(X, Ls, Rest).
Что можно переписать на что-то вроде:
occurrences(_, [], []).
occurrences(E, [X|Xs], Ys0) :-
reified_equality(Bool, E, X),
( Bool == true -> Ys0 = [X|Ys] ; Ys0 = Ys ),
% ( Bool = true, Ys0 = [X|Ys] ; Bool = true, Ys0 = Ys ),
occurrences(E, Xs, Ys).
reified_equality(R, X, Y) :- X == Y, !, R = true.
reified_equality(R, X, Y) :- ?=(X, Y), !, R = false.
reified_equality(true, X, X).
reified_equality(false, X, Y) :-
dif(X, Y).
Обратите внимание, что индексирование индекса второго индекса SWI активируется, после вводится запрос типа occurrences(_,[],_)
. Кроме того, SWI нуждается в неотъемлемо немонотонном if-then-else, поскольку он не индексируется на (;)/2
– дизъюнкции. SICStus делает это, но имеет только первый аргумент индексации. Таким образом, он оставляет один (1) выбор-точка открытым (в конце с []
).
Ну, во-первых, имя должно быть более декларативным, например equality_truth/2
.
Следующий код основан на if_/3
и (=)/3
(aka equal_truth/3
), как реализовано @false в Prolog union для AUBUC:
=(X, Y, R) :- X == Y, !, R = true.
=(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different
=(X, Y, R) :- X \= Y, !, R = false. % semantically different
=(X, Y, R) :- R == true, !, X = Y.
=(X, X, true).
=(X, Y, false) :-
dif(X, Y).
if_(C_1, Then_0, Else_0) :-
call(C_1, Truth),
functor(Truth,_,0), % safety check
( Truth == true -> Then_0 ; Truth == false, Else_0 ).
По сравнению с occurrences/3
вспомогательный occurrences_aux/3
использует другой порядок аргументов, который передает список Es
в качестве первого аргумента, который может включать индексацию первого аргумента:
occurrences_aux([], _, []).
occurrences_aux([X|Xs], E, Ys0) :-
if_(E = X, Ys0 = [X|Ys], Ys0 = Ys),
occurrences_aux(Xs, E, Ys).
Как указано @migfilg, цель Fs=[1,2], occurrences_aux(Es,E,Fs)
должна завершиться неудачно, поскольку она логически ложна:
occurrences_aux(_,E,Fs)
указывает, что все элементы из Fs
равны E
.
Однако сам по себе occurrences_aux/3
не заканчивается в таких случаях.
Мы можем использовать вспомогательный предикат allEqual_to__lazy/2
для улучшения поведения завершения:
allEqual_to__lazy(Xs,E) :-
freeze(Xs, allEqual_to__lazy_aux(Xs,E)).
allEqual_to__lazy_aux([],_).
allEqual_to__lazy_aux([E|Es],E) :-
allEqual_to__lazy(Es,E).
Со всеми вспомогательными предикатами на месте определите occurrences/3
:
occurrences(E,Es,Fs) :-
allEqual_to__lazy(Fs,E), % enforce redundant equality constraint lazily
occurrences_aux(Es,E,Fs). % flip args to enable first argument indexing
Пусть есть несколько запросов:
?- occurrences(E,Es,Fs). % first, the most general query
Es = Fs, Fs = [] ;
Es = Fs, Fs = [E] ;
Es = Fs, Fs = [E,E] ;
Es = Fs, Fs = [E,E,E] ;
Es = Fs, Fs = [E,E,E,E] ... % will never terminate universally, but ...
% that ok: solution set size is infinite
?- Fs = [1,2], occurrences(E,Es,Fs).
false. % terminates thanks to allEqual_to__lazy/2
?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1], E=1 ;
Fs = [2,2], E=2 ;
Fs = [3,3], E=3 ;
Fs = [], dif(E,1), dif(E,2), dif(E,3).
?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1]. % succeeds deterministically
?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E, E], Fs = [E,E], E1=E , E2=E ;
Es = [E, E2], Fs = [E], E1=E , dif(E2,E) ;
Es = [E1, E], Fs = [E], dif(E1,E), E2=E ;
Es = [E1,E2], Fs = [], dif(E1,E), dif(E2,E).
?- occurrences(1,[E1,1,2,1,E2],Fs).
E1=1 , E2=1 , Fs = [1,1,1,1] ;
E1=1 , dif(E2,1), Fs = [1,1,1] ;
dif(E1,1), E2=1 , Fs = [1,1,1] ;
dif(E1,1), dif(E2,1), Fs = [1,1].
Несколько запросов для тестирования, если универсальное завершение occurrences/3
завершается в определенных случаях:
?- occurrences(1,L,[1,2]).
false.
?- L = [_|_],occurrences(1,L,[1,2]).
false.
?- L = [X|X],occurrences(1,L,[1,2]).
false.
?- L = [L|L],occurrences(1,L,[1,2]).
false.
Кажется, лучше всего назвать этот предикат теми же аргументами (=)/3
. Таким образом, условия, подобные if_/3
, теперь более читабельны. И использовать вместо суффикса _t
вместо _truth
:
memberd_t(_X, [], false).
memberd_t(X, [Y|Ys], Truth) :-
if_( X = Y, Truth=true, memberd_t(X, Ys, Truth) ).
Раньше:
memberd_truth(_X, [], false).
memberd_truth(X, [Y|Ys], Truth) :-
if_( equal_truth(X, Y), Truth=true, memberd_truth(X, Ys, Truth) ).
ОБНОВЛЕНИЕ: Этот ответ был заменен моим 18 апреля. Я не предлагаю удалить его из-за комментариев ниже.
Мой предыдущий ответ был неправильным. Следующее идет против тестового примера в вопросе, и реализация имеет желаемую особенность, избегая лишних точек выбора. Я предполагаю, что режим верхнего предиката равен?, +,? хотя другие режимы могут быть легко реализованы.
В программе есть 4 предложения: список во втором аргументе посещается, и для каждого члена есть две возможности: он либо объединяется с 1-м аргументом верхнего предиката, либо отличается от него, и в этом случае a dif
применяется:
occurrences(X, L, Os) :- occs(L, X, Os).
occs([],_,[]).
occs([X|R], X, [X|ROs]) :- occs(R, X, ROs).
occs([X|R], Y, ROs) :- dif(Y, X), occs(R, Y, ROs).
Примеры запуска, используя YAP:
?- occurrences(1,[E1,1,2,1,E2],Fs).
E1 = E2 = 1,
Fs = [1,1,1,1] ? ;
E1 = 1,
Fs = [1,1,1],
dif(1,E2) ? ;
E2 = 1,
Fs = [1,1,1],
dif(1,E1) ? ;
Fs = [1,1],
dif(1,E1),
dif(1,E2) ? ;
no
?- occurrences(E,[E1,E2],Fs).
E = E1 = E2,
Fs = [E,E] ? ;
E = E1,
Fs = [E],
dif(E,E2) ? ;
E = E2,
Fs = [E],
dif(E,E1) ? ;
Fs = [],
dif(E,E1),
dif(E,E2) ? ;
no
Здесь еще более короткая логически-чистая реализация occurrences/3
.
Мы построим его на meta-predicate tfilter/3
,
предикат равенства терминов (=)/3
и сопрограмма allEqual_to__lazy/2
(определенная в моем предыдущем ответе на этот вопрос):
occurrences(E,Xs,Es) :-
allEqual_to__lazy(Es,E),
tfilter(=(E),Xs,Es).
Готово! Чтобы облегчить сравнение, мы повторно запускаем те же запросы, что и в предыдущем ответе:
?- Fs = [1,2], occurrences(E,Es,Fs).
false.
?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1], E=1 ;
Fs = [2,2], E=2 ;
Fs = [3,3], E=3 ;
Fs = [], dif(E,1), dif(E,2), dif(E,3).
?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1].
?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E, E ], Fs = [E,E], E1=E , E2=E ;
Es = [E, E2], Fs = [E], E1=E , dif(E2,E) ;
Es = [E1,E ], Fs = [E], dif(E1,E), E2=E ;
Es = [E1,E2], Fs = [], dif(E1,E), dif(E2,E).
?- occurrences(1,[E1,1,2,1,E2],Fs).
E1=1 , E2=1 , Fs = [1,1,1,1] ;
E1=1 , dif(E2,1), Fs = [1,1,1] ;
dif(E1,1), E2=1 , Fs = [1,1,1] ;
dif(E1,1), dif(E2,1), Fs = [1,1].
?- occurrences(1,L,[1,2]).
false.
?- L = [_|_],occurrences(1,L,[1,2]).
false.
?- L = [X|X],occurrences(1,L,[1,2]).
false.
?- L = [L|L],occurrences(1,L,[1,2]).
false.
Наконец, самый общий запрос:
?- occurrences(E,Es,Fs).
Es = Fs, Fs = [] ;
Es = Fs, Fs = [E] ;
Es = Fs, Fs = [E,E] ;
Es = Fs, Fs = [E,E,E] % ... and so on ad infinitum ...
Мы получаем те же ответы.
Реализация occurrences/3
ниже опирается на мои предыдущие ответы, а именно на получение прибыли от механизма индексирования предложения по 1-му аргументу, чтобы избежать некоторых точек выбора и устраняет все возникшие проблемы.
Кроме того, он справляется с проблемой во всех подчиненных реализациях до сих пор, включая тот, о котором идет речь в вопросе, а именно о том, что все они входят в бесконечный цикл, когда запрос имеет 2 первых аргумента бесплатно, а третий - с разными наземных элементов. Разумеется, правильное поведение - сбой.
Использование предиката сравнения
Я думаю, что для того, чтобы избежать неиспользованных точек выбора и сохраняя хорошую степень декларативности реализации, нет необходимости в предикате сравнения, предложенном в вопросе, но я согласен, что это может быть вопрос вкуса или наклон.
Реализация
В этом порядке рассматриваются три исключительных случая: если аргумент 2 аргументирован, то он посещается рекурсивно; в противном случае, если третий аргумент заземлен, он проверяется, а затем посещается рекурсивно; в противном случае для 2-го и 3-го аргументов создаются подходящие списки.
occurrences(X, L, Os) :-
( nonvar(L) -> occs(L, X, Os) ;
( nonvar(Os) -> check(Os, X), glist(Os, X, L) ; v_occs(L, X, Os) ) ).
При посещении земли 2-й аргумент имеет три случая, когда список не пуст: если его голова и X
выше являются грубыми и унифицируемыми, X
находится в начале результирующего списка вхождений, и нет другая альтернатива; в противном случае существуют две альтернативы: X
отличается от головы или объединяется с ней:
occs([],_,[]).
occs([X|R], Y, ROs) :-
( X==Y -> ROs=[X|Rr] ; foccs(X, Y, ROs, Rr) ), occs(R, Y, Rr).
foccs(X, Y, ROs, ROs) :- dif(X, Y).
foccs(X, X, [X|ROs], ROs).
Проверка основного аргумента состоит в том, чтобы убедиться, что все его члены объединяются с X
. В принципе эта проверка может выполняться с помощью glist/3
, но таким образом избегаются неиспользованные точки выбора.
check([], _).
check([X|R], X) :- check(R, X).
Посещение основного 3-го аргумента со свободным 2-м аргументом должно заканчиваться добавлением переменных, отличных от X
, в сгенерированный список. На каждом этапе рекурсии есть две альтернативы: текущая глава сгенерированного списка является текущим заголовком посещенного списка, который должен быть унифицирован с помощью X
или является свободной переменной, отличной от X
. Это теоретическое описание, потому что на самом деле существует бесконечное количество решений, и 3-е предложение никогда не будет достигнуто, когда голова списка является переменной. Поэтому приведено ниже третье предложение, чтобы избежать неприемлемых точек выбора.
glist([], X, L) :- gdlist(L,X).
glist([X|R], X, [X|Rr]) :- glist(R, X, Rr).
%% glist([X|R], Y, [Y|Rr]) :- dif(X, Y), glist([X|R], Y, Rr).
gdlist([], _).
gdlist([Y|R], X) :- dif(X, Y), gdlist(R, X).
Наконец, случай, когда все аргументы являются свободными, рассматривается таким же образом, как и предыдущий случай, и имеет аналогичную задачу о том, что некоторые шаблоны решений не создаются на практике:
v_occs([], _, []).
v_occs([X|R], X, [X|ROs]) :- v_occs(R, X, ROs).
%% v_occs([X|R], Y, ROs) :- dif(Y, X), v_occs(R, Y, ROs). % never used
Примеры тестов
?- occurrences(1,[E1,1,2,1,E2],Fs).
Fs = [1,1],
dif(E1,1),
dif(E2,1) ? ;
E2 = 1,
Fs = [1,1,1],
dif(E1,1) ? ;
E1 = 1,
Fs = [1,1,1],
dif(E2,1) ? ;
E1 = E2 = 1,
Fs = [1,1,1,1] ? ;
no
?- occurrences(1,L,[1,2]).
no
?- occurrences(1,L,[1,E,1]).
E = 1,
L = [1,1,1] ? ;
E = 1,
L = [1,1,1,_A],
dif(1,_A) ? ;
E = 1,
L = [1,1,1,_A,_B],
dif(1,_A),
dif(1,_B) ? ;
...