【问题标题】:swi-prolog doesn't stop after first true with disjunction (or)swi-prolog 在第一个 true 与析取(或)后不会停止
【发布时间】:2019-12-04 22:46:49
【问题描述】:

我(Prolog 绝对初学者)尝试从 prolog 中理解。这是swi-prolog 中的圣诞老人示例:

gives(santa,leonard,book).
gives(santa,adrian,game).
gives(santa,adrian,smartmax).

likes(leonard,lego).
likes(adrian,lego).
likes(adrian,book).

age(leonard,6).
age(adrian,4).

jealous(C1,C2) :- aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2,
  true.

jealous(C1,C2) :- findall(G, 
    (owns(C2,G), 
     likes(C1,G), 
     not(owns(C1,G))
    ), 
    Gifts),
  length(Gifts,NGifts),
  NGifts > 0,
  true.

这按预期工作:

?- jealous(adrian,leonard).
true.

但是,当我颠倒代码中两个jealous-predicates 的顺序时:

?- jealous(adrian,leonard).
true ;
false.

这很奇怪:我认为jealous-clauses 会表现为“或”:只要其中一个结果为真,就不应该进行任何进一步的处理。 所以我想知道:如何制作它以使其真正表现为“或”:如果jealous-规则中的任何一个为真,它应该返回true,在所有其他情况下,它是false .我不想要“下一个结果”。我只需要 1 个结果:truefalse

我做错了什么? (可能有几件事,所以请赐教:))。

谢谢。

【问题讨论】:

  • 您的实施中有一个选择点。这意味着 Prolog 在第一次成功后还有其他选择可以探索(真)。您的 ; 条目告诉 Prolog 返回选择点并检查更多解决方案。它再也找不到了,因此它以 false 响应。这是正常的 Prolog 行为。
  • 嗯,那个选择点取决于我定义的子句的顺序......我不明白为什么 Prolog 在某些情况下会返回,而在其他情况下不会。但是,似乎我找到了解决方案:stackoverflow.com/questions/25346189/prolog-disjunction.
  • 是的,没错...我了解;,但我不明白为什么Prolog 在找到true 时要继续。但显然,这是正常行为:p。
  • 在 Prolog 中编写“确定性”谓词这样的事情不会留下选择点。在不失一般性的情况下这样做可能非常具有挑战性(例如,如果您使用剪辑)。

标签: prolog prolog-toplevel


【解决方案1】:

按照顺序排列的子句,Prolog 的搜索首先尝试匹配第一个子句:

jealous(C1,C2) :- aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2.
  % the final true is unnecessary

该子句认为 adrian 有 2 个礼物和 leonard 1,因此 N1

?- jealous(adrian,leonard).
true.

此时已尝试所有子句,没有其他替代路径可以尝试解析查询,因此到此结束。

如果子句颠倒,则先尝试成功的,给出的解决方法:

?- jealous(adrian,leonard).
true

但存在第二个子句,但尚未尝试过,因此搜索算法为您提供了返回以耗尽第二个选项的选项。你做什么:

;
false.

尝试第二个子句并像以前一样失败,但它在第一个子句已经解决了查询之后才这样做。

行为上的差异还取决于算法如何优化其选择点记录。回溯是资源密集型的,Prolog 算法通过从选择点堆栈中丢弃确定性解决方案来优化这一点。也就是说,如果某处有语句

NGifts > 0

很明显,如果 NGifts 已知,则只有一种可能的解决方案(无论哪种),此时回溯将不会返回替代证明。因此,该语句从选择堆栈中被丢弃,因为它是确定性的 - 它的唯一解决方案已被探索。

当子句按照您的问题排序时,当两个子句都被调查并给出解决方案时,堆栈中没有选择点并且定理证明器退出。但是如果你交换它们,在尝试第二个子句之前会在第一个子句中找到一个解决方案,因此第二个嫉妒子句的存在留下了一个可供探索的选择点,这是向你提出的。

如果您需要控制这种行为,并且不需要识别小男孩相互嫉妒的情况,您可以强制 Prolog '删除'选择点:

jealous(C1,C2) :-
  findall(G, 
    (owns(C2,G), 
     likes(C1,G), 
     not(owns(C1,G))
    ), 
    Gifts),
  length(Gifts,NGifts),
  NGifts > 0,
  !. % cut: if this clause gets so far, later clauses do not need to be explored

jealous(C1,C2) :-
  aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2.

这使得本例中两个程序的行为相同。我将“小心削减”的讨论留给 cmets 和其他问题。

【讨论】:

  • 很好的解释!小心削减,我得到那个,因为它在很大程度上取决于条款的顺序。在查询中使用“一次”是否更有意义?
  • @KurtSys 的问题(切割和一次)是它影响 Prolog 作为定理证明器的工作。逻辑编程(作为一个领域)应该允许您独立于算法执行来思考要解决的问题。如果你需要'once',或者一个cut,那么你的程序依赖于执行顺序,所以它不是你的问题的陈述,而是一个解决它的程序。
  • 好吧,我明白了……(感觉顺序很重要是错误的)。 jealous 子句不是问题陈述,而是解决问题的程序。我知道。那么,如果它们不应该出现在条款中,我应该把它们放在哪里? (或者我怎样才能更好地写出“问题陈述”?
  • @KurtSys 寻找简化代码的方法,移动复杂代码以将丑陋的计算隔离在单独的规则或子句中,并根据您的意图命名规则。实际上,您的第一个子句不需要所有计数的东西。您所需要的只是拥有/喜欢/不拥有。如果你得到几个嫉妒的“证据”,那么请重新考虑:每个证据都是嫉妒的理由,再加上一个衡量嫉妒有多严重的分数。所以 NGifts 是一种嫉妒指标。
  • 现在对我来说完全有意义。用户可以选择如何处理所有“真实”证明(或者,如果添加一个指标,则对所有指标做某事)。但是,这给我留下了另一个问题:我可以将所有证明绑定在一个列表中吗?到目前为止,N1&lt;N2owns/likes/not owns 返回 true 或 false,但 Prolog 并没有将它们“收集”到一个列表中,对吧?使用findall(和类似的)似乎很难,因为没有变量。 findall(_, jealous(adrian, lennert), J). 的结果类似于 J = [_3114]。哦,这是否意味着:1 true 值?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2019-10-17
  • 2016-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多