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

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

son(a, d).
son(b, d).
son(a, c).
son(b, 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;

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

6 ответов

Решение

Я получил ответ.

% 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)).

Он всегда возвращает неудачу в конце, но это происходит с помощью следующего.

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

Это терпит неудачу со следующим:

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

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

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

Так и есть.

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

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 - останавливает "горизонтальное" расширение, как только ветвь будет признана допустимой, или добавляет какое-либо утверждение, которое позволяет избежать нескольких решений.

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

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

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

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

?- setof(t,brother(X,Y),_).
X = a, Y = b ;
X = b, Y = a.

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

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,

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

brother(X, Y) :-
    son(X, P1),
    son(Y, P1),
    X @< Y,
    (son(X, P2), son(Y, P2), P1 @< P2 -> false; true).

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

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

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

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

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

Другие вопросы по тегам