Я пытаюсь построить простой предикат, который получает в качестве входных данных два списка, а результаты - третий, состоящий из пересечения первых двух.
Я решил сделать это с помощью логического оператора. Я уверен, что моя логика правильная, но мой предикат не работает. Любые идеи?:
Пожалуйста, не публикуйте альтернативные методы. Мне интересно, почему этот возвращает FALSE каждый раз.
Ответ 3
Отсутствие обратного отслеживания из-за отрицания, как указано @SQB, на самом деле не единственная проблема с вашим кодом. Если вы немного поиграете с наземными запросами, вы обнаружите, что не-списки и пустой список, как указано @false, также не являются единственной проблемой. Рассмотрим следующие запросы:
?- intersection([2,3],[1,2,3],[2,3,4]).
yes
?- intersection([2],[1,2,3],[2,3,4]).
yes
?- intersection([3],[1,2,3],[2,3,4]).
yes
?- intersection([],[1,2,3],[2,3,4]).
yes
Первое - это то, что обычно понимается как пересечение. Остальные три являются подсписками пересечения, включая тривиальный подсписк []
. Это связано с тем, как предикат описывает, что такое пересечение: в пересечении не так, что элемент находится в первом, но не во втором списке И, что указанный элемент находится в первом, но не в третьем списке. Это описание четко соответствует трем вышеуказанным запросам, поэтому они преуспевают. Обманывая немного больше с этим описанием в виду, есть некоторые другие заслуживающие внимания наземные запросы, которые преуспевают:
?- intersection([2,2,3],[1,2,3],[2,3,4]).
yes
Вопрос о том, является ли наличие дубликатов в решении приемлемым или нет, на самом деле является предметом обсуждения. Списки [2,2,3]
и [2,3]
, хотя разные, представляют один и тот же набор {2,3}. Существует этот последний ответ на вопрос о союзе Prolog, который разрабатывает такие аспекты ответов. И, конечно, подсписки упомянутого выше пересечения могут также содержать дубликаты или кратность:
?- intersection([2,2,2],[1,2,3],[2,3,4]).
yes
Но почему это? Для пустого списка это довольно легко увидеть. Запрос
?- element(A,[]).
no
выходит из строя, поэтому конъюнкция element(A,L1), not(element(A,L2))
также не выполняется для L1=[]
. Поэтому отрицание, обернутое вокруг, преуспевает. То же самое верно для второго отрицания, следовательно []
может быть выведено как пересечение. Чтобы понять, почему [2]
и [3]
преуспеть в качестве пересечения, полезно написать свой предикат как логическую формулу с явно выраженными кванторами квантов:
∀ L1
∀ L2
∀ R
∀ A
(intersection(L1,L2,R)
← ¬ (element(A,L1)
∧ ¬ element(A,L2))
∧ ¬ (element(A,L1)
∧ ¬ element(A,R)))
Если вы обратитесь к учебнику по логике или к логическому программированию, который также показывает код Пролога как логические формулы, вы обнаружите, что универсальные кванторы для переменных, которые не встречаются в главе правила, могут быть перемещены в тело как экзистенциальные кванторы. В этом случае для A
:
∀ L1
∀ L2
∀ R
(intersection(L1,L2,R)
← ∃ A (
¬ (element(A,L1)
∧ ¬ element(A,L2))
∧ ¬ (element(A,L1)
∧ ¬ element(A,R))))
Итак, для всех аргументов L1,L2,R
существует некоторая A
, которая удовлетворяет целям. Это объясняет вывод сублистов пересечения и множественных вхождений элементов.
Однако гораздо более раздражает вопрос о том, что запрос
?- intersection(L1,[1,2,3],[2,3,4]).
вместо создания решений. Если вы считаете, что L1
не создается и не просматривает результаты для следующего запроса
?- element(A,L1).
L1 = [A|_A] ? ;
L1 = [_A,A|_B] ? ;
L1 = [_A,_B,A|_C] ? ;
...
становится ясно, что запрос
?- element(A,L1),not(element(A,[1,2,3])).
должен зацикливаться из-за бесконечного числа списков L1
, которые содержат A
, описанные первым кликом. Следовательно, соответствующая конъюнкция в вашем предикате также должна зацикливаться. Кроме того, было бы неплохо, если бы такой предикат отражал реляционную природу Prolog и работал и наоборот (переменная 2-го или 3-го аргументов). Позвольте сравнить ваш код с таким решением. (Для сравнения следующий предикат описывает подсписки пересечения так же, как и ваш код, для другого определения см. Далее ниже.)
Чтобы отразить его декларативный характер, назовите его list_list_intersection/3:
list_list_intersection(_,_,[]).
list_list_intersection(L1,L2,[A|As]) :-
list_element_removed(L1,A,L1noA),
list_element_removed(L2,A,L2noA),
list_list_intersection(L1noA,L2noA,As).
list_element_removed([X|Xs],X,Xs).
list_element_removed([X|Xs],Y,[X|Ys]) :-
dif(X,Y),
list_element_removed(Xs,Y,Ys).
Как и ваш предикат, эта версия также использует элементы пересечения для описания отношения. Следовательно, он создает одни и те же подсписки (включая []
):
?- list_list_intersection([1,2,3],[2,3,4],I).
I = [] ? ;
I = [2] ? ;
I = [2,3] ? ;
I = [3] ? ;
I = [3,2] ? ;
no
но без циклов. Тем не менее, множественные вхождения больше не производятся, поскольку уже сопоставленные элементы удаляются с помощью list_element_removed/3. Но несколько вхождений в обоих первых списках согласованы правильно:
?- list_list_intersection([1,2,2,3],[2,2,3,4],[2,2,3]).
yes
Этот предикат также работает в других направлениях:
?- list_list_intersection([1,2,3],L,[2,3]).
L = [2,3|_A] ? ;
L = [2,_A,3|_B],
dif(_A,3) ? ;
L = [2,_A,_B,3|_C],
dif(_A,3),
dif(_B,3) ? ;
...
?- list_list_intersection(L,[2,3,4],[2,3]).
L = [2,3|_A] ? ;
L = [2,_A,3|_B],
dif(_A,3) ? ;
L = [2,_A,_B,3|_C],
dif(_A,3),
dif(_B,3) ? ;
...
Итак, эта версия соответствует вашему коду без дубликатов. Обратите внимание, как элемент A
пересечения явно появляется в начале правила, где все элементы пересечения проходят рекурсивно. По моему мнению, это то, чего вы пытались достичь, используя неявные универсальные кванторы перед правилами Prolog.
Чтобы вернуться к точке в начале моего ответа, это не то, что обычно понимается как пересечение. Среди всех результатов list_list_intersection/3 для аргументов [1,2,3]
и [2,3,4]
только [2,3]
- это пересечение. Здесь возникает другая проблема с вашим кодом: если вы используете элементы пересечения для описания отношения, как вы убедитесь, что вы покрываете все пересекающиеся элементы? В конце концов, все элементы [2]
встречаются в [1,2,3]
и [2,3,4]
. Очевидная идея состояла бы в том, чтобы пройти через элементы одного из других списков и описать те, которые происходят в обоих, также находясь на пересечении. Вот вариант с if_/3 и memberd_t/3:
list_list_intersection([],_L2,[]).
list_list_intersection([X|Xs],L2,I) :-
if_(memberd_t(X,L2),
(I=[X|Is],list_element_removed(L2,X,L2noX)),
(I=Is,L2noX=L2)),
list_list_intersection(Xs,L2noX,Is).
Обратите внимание, что также можно пройти через аргументы второго списка вместо первого. Предикат memberd_t/3 является reified вариантом вашего предикатного элемента /2, а list_element_removed/3 снова используется в описании, чтобы избежать дублирования в решении. Теперь решение уникально
?- list_list_intersection([1,2,3],[2,3,4],L).
L = [2,3] ? ;
no
и "проблемные запросы" сверху выходят из строя, как ожидалось:
?- list_list_intersection([1,2,3],[2,3,4],[]).
no
?- list_list_intersection([1,2,3],[2,3,4],[2]).
no
?- list_list_intersection([1,2,3],[2,3,4],[3]).
no
?- list_list_intersection([1,2,3],[2,3,4],[2,2,3]).
no
?- list_list_intersection([1,2,3],[2,3,4],[2,2,2]).
no
И, конечно, вы также можете использовать предикат в других направлениях:
?- list_list_intersection([1,2,3],L,[2,3]).
L = [2,3] ? ;
L = [3,2] ? ;
L = [2,3,_A],
dif(_A,1) ? ;
...
?- list_list_intersection(L,[2,3,4],[2,3]).
L = [2,3] ? ;
L = [2,3,_A],
dif(4,_A) ? ;
...