不要在Prolog中重复解决方案

时间:2013-02-23 15:28:44

标签: prolog prolog-setof

假设您有一个包含以下内容的数据库:

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;

我能理解为什么我会得到这些结果,但我正在寻找一种方法来解决这个问题。我该怎么办?

6 个答案:

答案 0 :(得分:4)

您可以通过比较消除一组:

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.

此方法非常特定于此特定问题,但基本推理不是:您有两个副本,因为ab是&#34;兄弟&#34;通过cd - Prolog两次生成该解决方案是正确的,因为有一个隐藏变量被实例化为两个不同的值。

更优雅的解决方案可能是使用setof/3来获得解决方案。即使使用原始代码,这也可以使用:

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

这种方法的缺点是你最终会使用列表而不是Prolog生成不同的解决方案,尽管你可以使用member/2恢复该行为。

答案 1 :(得分:4)

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运算符 - 一旦发现分支有效就停止“水平”扩展,或添加一些避免多个解决方案的语句。

有关详细信息,请查看Unification算法。

答案 2 :(得分:4)

首先,我建议不要动态更新Prolog数据库。出于某些原因,请考虑该文章 "How to deal with the Prolog dynamic database?"

可以使用内置setof/3member/2的组合,正如@DanielLyons在他的回答中建议的那样。

作为另一种选择,请考虑以下以不同寻常的方式使用setof/3的查询,如下所示:

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

答案 3 :(得分:0)

这应该有效。但我认为它可以改进(我不是Prolog专家):

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

答案 4 :(得分:0)

如果您正在使用Strawberry Prolog编译器,则输入以下内容将无法获得所有答案:

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

为了得到所有答案,请写下:

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

我希望它可以帮到你。:)

答案 5 :(得分:-2)

我得到了答案。

% 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的每个兄弟都没有重复
  • 兄弟('Urraca','Sancho I')。 %真,因为Urraca和Sancho我有同样的父亲和母亲。事实上,即使他们只有同一个母亲或同一个父亲,它也会回归真实。有点偏离背景但仍然有效,如果他们有三个或更多共同的父母,它仍然可以工作

失败并出现以下情况:

  • 兄弟(X,X)。 %False,因为它是同一个人
  • 兄弟('Nope',X)。 %False因为数据库中没有,
  • 兄弟('Nope','Sancho I')。 %False,同样的原因

所以像这样我可以问:兄弟(X,Y),并开始按“;”看到每个兄弟姐妹都没有重复。

我也可以做兄弟(a,b)和兄弟(b,a),假设a和b是数据库中的人。这很重要,因为有些解决方案会使用@&lt;测试事情,所以兄弟(b,a)会失败。

就是这样。