0

我有一个这样的事实清单:

set(h, 3).
set(h, 6).
set(h, 12).
set(h, 1).
set(h, 7).

我需要找到集合 h 的最大值,查询需要如下所示:

?- maximum(h, Max).
Max = 12.

显然,现在有很多方法可以做到这一点。但目前我正在寻找一种使用动态谓词和失败谓词的方法。我猜我可能不得不使用重复?不幸的是,重复谓词和动态谓词都让我感到困惑。

我在想这样的事情:

set(h, 3).
set(h, 6).
set(h, 12).
set(h, 1).
set(h, 7).

:- dynamic current_max/1.

assert(current_max(0)).

maximum(Set, Element):-
    repeat,
    set(Set,Element),
    current_max(Max),
    (Max > Element,
        fail);
     (Element > Max,
     retract(current_max(Max)),
     assert(current_max(Element)),
     fail).

maximum(_, Max):-
    current_max(Max).

我自己看到的问题之一是重复循环不会停止。如果我能以某种方式知道它是集合中的最后一个元素,我可能会使用 cut,但我不确定如何使用。

4

3 回答 3

3

故障驱动的循环如下所示:

(   Generator,
    Side_effect,
    fail
;   true
)

在您的情况下set/2是生成器,更新当前最大值是副作用。在这种情况下您不需要repeat:调用set/2将生成选择点。repeat当您必须在没有生成器的情况下创建选择点时,您需要。

再次,我不得不说这是一个非常奇怪的问题(你之前也问过)。如果你想做这样的事情,至少不要使用动态谓词来保持当前最大值:这只是在自找麻烦。

在 SWI-Prolog 的库(聚合)中有这样做的例子。作为aggregate_all/3起点,只留下查找最大数的代码,您有:

set(h, 3).
set(h, 6).
set(h, 12).
set(h, 1).
set(h, 7).

maximum(Set, Max) :-
        State = state(Max),
        (       set(Set, Max),
                arg(1, State, M0),
                M is max(M0, Max),
                nb_setarg(1, State, M),
                fail
        ;       arg(1, State, Max),
                nonvar(Max)
        ).

这使用一个术语来保持当前最大值,因此您保持的状态是maximum/2谓词的本地状态。它也没有假设初始值(如果你从 0 开始,就像你做的那样,并且所有值恰好都是负数,你会得到一个不正确的结果!)。有了这个定义:

?- maximum(h, M).
M = 12.

?- maximum(x, M).
false.

几点评论:

nonvar(Max)当在析取开始时调用set/2失败时,您需要在最后。但是请注意,就目前而言,您仍然可能得到错误的结果:

?- maximum(x, 42).
true.

你应该能够弄清楚如何处理这个问题。

目前,这使用算术函数max来获得两个数字中的较大者。如果您有其他任何东西,例如原子,这将不起作用(即使原子有定义的总排序)。您可以定义一个谓词来查找任意两个项中的最大值:

term_max(X, Y, Max) :-
    (   X @> Y
    ->  Max = X
    ;   Max = Y
    ).

然后,你只需更换

M is Max(M0, Max)

和:

term_max(M0, Max, M)
于 2016-12-01T08:28:00.677 回答
0

好的。这几天我一直在看这个。我想我明白为什么我们不需要重复了。我尝试将我的(坏)方法与您的一些想法结合起来。

这就是我想出的:

maximum(Set, Element):-
    ((set(Set,Element),
        current_max(Max),
        (Max > Element,
            fail);
         (Element > Max,
         retract(current_max(Max)),
         assert(current_max(Element)),
         fail))
     ;
     true
     ).

maximum(_, Max):-
    current_max(Max).)

不幸的是,现在它给了我“参数没有充分实例化”。

这就是我的想法: set/2 生成你的值(可以给它,元素从事实中获取第一个值)。然后它从 current_max/1 中取一个值(最初是 0)。如果最大值大于它失败并从集合中获取另一个值。如果 Element 大于 Max,我们删除之前的 current_max 事实并创建新的。然后我们也失败了,然后回去。一旦所有值都通过它返回true。然后我希望它也能通过第二条规则给出 Max 答案。

于 2016-12-05T16:55:30.740 回答
0

一个低效但可能对教学有用的解决方案是用于forall/2为您找到正确的值:

maximum(Set, Max) :-
  set(Set, Max),
  forall(set(Set, Other), 
           Max >= Other).

Prolog 将(低效地)选择每个元素作为候选元素,Max直到forall/2满足该步骤,所以这绝对是一个二次时间解决方案,但在我看来,它比故障驱动循环更好地说明了“Prolog 思维”。

repeat/0确实是 Prolog 中的一种低级机器。大多数时候,通过逻辑思考问题并围绕该逻辑构建解决方案,您可以获得更好的结果,而不是预先担心 Prolog 的解析系统将如何制作结果。在前一种方法中,您仍然需要了解回溯以及 Prolog 将如何重试事物,但您使用它,而不是对抗它或尝试直接利用它。

于 2016-12-14T21:01:09.483 回答