Не повторяйте решения на Прологе

Предположим, у вас есть база данных со следующим содержимым:

son(a, d).
son(b, d).
son(a, c).
son(b, c).

Итак, a и b являются сыновьями d и c. Теперь вы хотите знать, учитывая большую базу данных, кто кому брат. Решение будет:

brother(X, Y) :-
    son(X, P),
    son(Y, P),
    X \= Y.

Проблема в том, что если вы спросите «брат (X, Y)». и начните нажимать ";" вы получите избыточные результаты, такие как:

  • X = a, Y = b;
  • X = b, Y = a;
  • X = a, Y = b;
  • X = b, Y = a;

Я могу понять, почему я получаю такие результаты, но я ищу способ исправить это. Что я могу сделать?


person petermlm    schedule 23.02.2013    source источник
comment
Добавление ссылки на то, почему результаты повторяются, было бы полезно для читателей.   -  person GiriB    schedule 31.01.2017


Ответы (6)


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

son(a, d).
son(b, d).
son(a, c).
son(b, c).

brother(X, Y) :-
    son(X, P),
    son(Y, P),
    X \= Y.

                         brother(X, Y)
       _______________________|____________________________        [son(X, P)]
      |               |                  |                 |
X = a, P = d     X = b, P = d       X = a, P = c      X = a, P = b
      |               |                  |                 |  
      |              ...                ...               ...
      |
      | (X and P are already defined for this branch;
      |  the algorithm now looks for Y's)
      |__________________________________________                  [son(Y, d)]
                |                                |
      son(a, d) -> Y = a               son(b, d) -> Y = b
                |                                |
                |                                |                 [X \= Y]
      X = a, Y = a -> false            X = a, Y = b -> true
                                                 |
                                                 |
                                  solution(X = a, Y = b, P = d)

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

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

Для получения дополнительной информации взгляните на алгоритм объединения.

person Rubens    schedule 23.02.2013
comment
Я действительно понял это. Я пытался найти способ решить проблему, и я сделал. Спасибо за очень развернутый ответ. - person petermlm; 25.02.2013

Во-первых, я бы посоветовал не обновлять базу данных Prolog динамически. По некоторым причинам рассмотрите статью "Как обращаться с динамической базой данных Prolog?".

Вы можете использовать комбинацию встроенных setof/3 и member/2, как предложил @DanielLyons в своем ответе.

В качестве еще одной альтернативы рассмотрим следующий запрос, в котором setof/3 используется довольно необычным образом, например так:

?- setof(t,brother(X,Y),_).
X = a, Y = b ;
X = b, Y = a.
person repeat    schedule 29.04.2015
comment
Мне понравилось это решение. Я думаю, что этот случай тоже можно решить, используя bagof вместо setof. Я ошибся? - person eightShirt; 21.11.2020

Вы можете исключить один набор с помощью сравнения:

brother(X, Y) :-
   son(X, P),
   son(Y, P),
   X \= Y, X @< Y.

?- brother(X, Y).
X = a,
Y = b ;
X = a,
Y = b ;
false.

Поскольку X и Y будут реализованы в обоих направлениях, требование, чтобы X было меньше Y, является хорошим способом сократить количество решений вдвое.

Ваша вторая проблема заключается в том, что X и Y являются братьями более чем по одному родителю. Самым простым решением здесь было бы сделать ваши правила более явными:

mother(a, d).
mother(b, d).
father(a, c).
father(b, c).

brother(X, Y) :-
  mother(X, M), mother(Y, M),
  father(X, F), father(Y, F),
  X \= Y, X @< Y.

?- brother(X, Y).
X = a,
Y = b ;
false.

Этот метод очень специфичен для этой конкретной проблемы, но лежащие в его основе рассуждения не таковы: у вас было две копии, потому что a и b являются «братьями» по c, а также по d — Пролог был прав, создав это решение дважды, потому что была скрытая переменная. присваивается два разных значения.

Более элегантным решением, вероятно, было бы использование setof/3 для получения решений. Это может работать даже с вашим исходным кодом:

?- setof(X-Y, (brother(X, Y), X @< Y), Brothers).
Brothers = [a-b].

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

person Daniel Lyons    schedule 23.02.2013
comment
Это может решить проблему повторяющихся решений, но порождает другую. Спрашивая :- брат('а', 'б'). возвращает true, но спрашивает брата ('b', 'a'). возвращает ложь. Но спасибо за ответ. Это хороший и простой трюк, который решает часть того, что я хотел. - person petermlm; 25.02.2013
comment
Это правда, но в стиле Пролога было бы очень плохо хотеть, чтобы brother(b, a) было истинным, но не генерировалось brother(X, Y). В общем, вы не хотите генерировать приемлемые решения. - person Daniel Lyons; 25.02.2013

Это должно работать. Но я думаю, что это можно улучшить (я не специалист по Прологу):

brother(X, Y) :-
    son(X, P1),
    son(Y, P1),
    X @< Y,
    (son(X, P2), son(Y, P2), P1 @< P2 -> false; true).
person Benoît Guédas    schedule 23.02.2013
comment
Как я уже сказал @Daniel Lyons. Это также хорошее решение проблемы, но оно порождает другую. Спрашивая :- брат('а', 'б'). возвращает true, но спрашивает брата ('b', 'a'). возвращает ложь. Спасибо за ответ, я не знал обозначения вашей последней строки. - person petermlm; 25.02.2013

Если вы используете компилятор Strawberry Prolog, вы не получите все ответы, набрав это:

?- brother(X, Y),
   write(X), nl,
   write(Y), nl.

Чтобы получить все ответы, напишите это:

?- brother(X, Y),
   write(X), nl,
   write(Y), nl,
   fail.

Я надеюсь, что это поможет вам. :)

person Faeze    schedule 23.02.2013
comment
На самом деле я использую swipl и не могу проверить ваше решение. Но всегда приятно иметь ответ для множества доступных платформ! - person petermlm; 25.02.2013

Я пришел к ответу.

% Include the dictionary
:- [p1]. % The dictionary with sons

:- dynamic(found/2).

brother(X, Y) :-
    % Get two persons from the database to test
    son(X, P),
    son(Y, P),

    % Test if the two persons are different and were not already used
    testBrother(X, Y).

% If it got here it's because there is no one else to test above, so just fail and retract all
brother(_, _) :-
    retract(found(_, _)),
    fail.

testBrother(X, Y) :-
    X \= Y,
    \+found(X, Y),
    \+found(Y, X),

    % If they were not used succed and assert what was found
    assert(found(X, Y)).

Он всегда возвращает сбой в конце, но он преуспевает в следующем.

  • брат(Х, Y). % Каждый брат без повторения
  • брат('Уррака', X). % Каждый брат Урраки без повторения
  • брат («Уррака», «Санчо I»). % Верно, ведь у Уррака и Санчо у меня одни и те же отец и мать. На самом деле, даже если бы у них была только одна и та же мать или один и тот же отец, это вернет правду. Немного не в контексте, но все же верно, если у них три или более общих родителей, это все равно будет работать.

Это не удается со следующим:

  • брат(Х, Х). % Неверно, потому что это один и тот же человек
  • брат('Нет', X). % False, потому что нет даже в базе данных
  • брат («Нет», «Санчо I»). % Ложь по той же причине

Таким образом, я могу, например, спросить: брат (X, Y) и начать нажимать «;» увидеть каждого брата и сестру без повторения.

Я также могу выполнить команду Brother(a, b) и Brother(b, a), предполагая, что a и b являются людьми в базе данных. Это важно, потому что некоторые решения будут использовать @‹ для проверки вещей, и, например, Brother(b, a) потерпит неудачу.

Вот оно.

person petermlm    schedule 23.02.2013
comment
Это работает, но это не то, что я хотел бы делать в своем собственном коде. Я считаю динамическое хранилище своего рода последним средством, способом обойти обычную унификацию try/bind/fail/unbind, которую хочет сделать Пролог. Наличие динамического состояния, которое волшебным образом появляется и исчезает во время того, что кажется чисто логическими предикатами, — это много механизмов и много мест, где можно спрятать ошибки. Если бы я увидел это в кодовой базе, я бы забеспокоился, что подобные вещи могут происходить повсюду, что затрудняет изоляцию и отладку программного обеспечения. - person Daniel Lyons; 25.02.2013
comment
Я понимаю. Спасибо за ваш отзыв (здесь и в другом комментарии). Я согласен с тем, что вы сказали, но мне действительно нужно было это, как я показал. - person petermlm; 25.02.2013