【问题标题】:Don't repeat solutions in Prolog不要在 Prolog 中重复解决方案
【发布时间】:2013-02-23 15:28:43
【问题描述】:

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

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;

我可以理解为什么会得到这些结果,但我正在寻找解决此问题的方法。我能做什么?

【问题讨论】:

  • 添加结果重复原因的链接将对读者有所帮助。

标签: prolog prolog-setof


【解决方案1】:

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

您也可以使用! -- 剪切运算符 -- 一旦发现分支有效,就会停止“水平”扩展,或者添加一些避免多个解决方案的语句。

如需更多信息,请查看Unification 算法。

【讨论】:

  • 我其实明白这一点。我试图找到解决问题的方法,我做到了。感谢您非常详尽的回答。
【解决方案2】:

首先,我建议不要动态更新 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.

【讨论】:

  • 我喜欢这个解决方案。我认为这种情况也可以使用 bagof 而不是 setof 来解决。我错了吗?
【解决方案3】:

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

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.

此方法非常针对这个特定问题,但根本原因不是:您有两个副本,因为 abcd 的“兄弟”——Prolog 是对的生成该解决方案两次,因为有一个隐藏变量被实例化为两个不同的值。

更优雅的解决方案可能是使用setof/3 来获取解决方案。这甚至可以使用您的原始代码:

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

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

【讨论】:

  • 这可以解决重复解决方案但引发另一个问题。问“:-兄弟('a','b')。”返回真,但询问“兄弟('b','a')。”返回假。但感谢您的回答。这是一个不错的简单技巧,可以解决我想要的部分问题。
  • 是的,但如果希望brother(b, a) 为真但不是由brother(X, Y) 生成,那将是非常糟糕的Prolog 风格。一般来说,您不希望生成可接受的解决方案。
【解决方案4】:

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

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

【讨论】:

  • 就像我对@Daniel Lyons 所说的那样。这也是该问题的一个很好的解决方案,但它引发了另一个问题。问“:-兄弟('a','b')。”返回真,但询问“兄弟('b','a')。”返回假。谢谢你的回答,我不知道你最后一行的符号。
【解决方案5】:

如果您使用的是 Strawberry Prolog 编译器,您将无法通过键入以下内容获得所有答案:

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

为了得到所有的答案,写这个:

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

希望对你有帮助。:)

【讨论】:

  • 其实我正在使用 swipl 并且无法测试您的解决方案。但是,为许多可用平台提供答案总是很高兴!
【解决方案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)。 % 兄弟不重复
  • 兄弟('乌拉卡',X)。 % Urraca 的每个兄弟都没有重复
  • 兄弟(“乌拉卡”,“桑乔一世”)。 % 是的,因为乌拉卡和桑乔我有同一个父亲和母亲。事实上,即使他们只有同一个母亲或同一个父亲,它也会返回真实的。有点脱离上下文但仍然有效,如果他们有三个或更多共同的父母,它仍然可以工作

它失败了:

  • 兄弟(X,X)。 % 错误,因为是同一个人
  • 兄弟('不',X)。 % False 因为 not 甚至不在数据库中
  • 兄弟('不','桑乔一世')。 % 错误,同样的原因

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

我也可以做兄弟(a,b)和兄弟(b,a),假设a和b是数据库中的人。这很重要,因为某些解决方案会使用 @

原来如此。

【讨论】:

  • 它有效,但我不想在我自己的代码中这样做。我认为动态存储是一种最后的手段,一种绕过 Prolog 想要做的通常的 try/bind/fail/unbind 统一的方法。在看似纯逻辑的谓词期间,具有神奇地出现和消失的动态状态是很多机器和很多隐藏错误的地方。如果我在代码库中看到这一点,我会担心这样的事情可能会到处发生,从而使软件难以隔离和调试。
  • 我明白了。感谢您的反馈(在这里和其他评论)我同意您所说的,但我真的需要这个,就像我展示的那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多