从谓词中收集所有“最小”解决方案

时间:2014-12-05 13:34:00

标签: prolog backtracking aggregates prolog-setof meta-predicate

鉴于数据库中存在以下事实:

foo(a, 3).
foo(b, 2).
foo(c, 4).
foo(d, 3).
foo(e, 2).
foo(f, 6).
foo(g, 3).
foo(h, 2).

我想收集所有第一个具有最小第二个参数的参数,再加上第二个参数的值。首先尝试:

find_min_1(Min, As) :-
    setof(B-A, foo(A, B), [Min-_|_]),
    findall(A, foo(A, Min), As).

?- find_min_1(Min, As).
Min = 2,
As = [b, e, h].

我可以使用setof/3

而不是aggregate/3
find_min_2(Min, As) :-
    aggregate(min(B), A^foo(A, B), Min),
    findall(A, foo(A, Min), As).

?- find_min_2(Min, As).
Min = 2,
As = [b, e, h].

NB

如果我正在寻找数字的最小值,这只会得到相同的结果。如果涉及算术表达式,结果可能会有所不同。如果涉及非数字,aggregate(min(...), ...)将引发错误!

或者,我可以使用完整的按键排序列表:

find_min_3(Min, As) :-
    setof(B-A, foo(A, B), [Min-First|Rest]),
    min_prefix([Min-First|Rest], Min, As).

min_prefix([Min-First|Rest], Min, [First|As]) :-
    !,
    min_prefix(Rest, Min, As).
min_prefix(_, _, []).

?- find_min_3(Min, As).
Min = 2,
As = [b, e, h].

最后,问题是:

  • 我可以直接使用库(聚合)吗?感觉应该是可能的....

  • 或者是否有来自C ++标准库的std::partition_point谓词?

  • 或者有更简单的方法吗?

编辑:

更具描述性。假设有一个(库)谓词partition_point/4

partition_point(Pred_1, List, Before, After) :-
    partition_point_1(List, Pred_1, Before, After).

partition_point_1([], _, [], []).
partition_point_1([H|T], Pred_1, Before, After) :-
    (   call(Pred_1, H)
    ->  Before = [H|B],
        partition_point_1(T, Pred_1, B, After)
    ;   Before = [],
        After = [H|T]
    ).

(我不喜欢这个名字,但我们现在可以忍受它了)

然后:

find_min_4(Min, As) :-
    setof(B-A, foo(A, B), [Min-X|Rest]),
    partition_point(is_min(Min), [Min-X|Rest], Min_pairs, _),
    pairs_values(Min_pairs, As).

is_min(Min, Min-_).

?- find_min_4(Min, As).
Min = 2,
As = [b, e, h].

3 个答案:

答案 0 :(得分:4)

  

这类问题的惯用方法是什么?

     

有没有办法简化问题?

以下许多评论可以在SO上添加到许多程序中。

势在必行的名字

每次,你都会为一些关系写一个命令性名称,这会减少你对关系的理解。不多,只是一点点。许多常见的Prolog习语如append/3都没有树立好榜样。想想append(As,As,AsAs)find_min(Min, As)的第一个参数是最小值。所以minimum_with_nodes/2可能是一个更好的名字。

findall/3

除非严格检查用途,否则不要使用findall/3,基本上所有东西都必须磨削。在你的情况下它恰好工作。但是一旦你稍微概括foo/2,你就会失败。这经常是一个问题:你写一个小程序;它似乎工作。 一旦你转向更大的,相同的方法不再有效。 findall/3(与setof/3相比)就像一家中国商店的公牛一样,粉碎了共享变量和量化的优良结构。另一个问题是意外失败不会导致findall/3失败,这往往导致奇怪的,难以想象的角落案件。

不稳定,太具体的程序

另一个问题也与findall/3有些相关。你的程序是如此具体,你将永远测试它是不太可能的。边际变化将使您的测试无效。所以你很快就会放弃进行测试。让我们看看具体是什么:主要是foo/2关系。是的,只是一个例子。考虑如何设置foo/2可能发生变化的测试配置。每次更改(写一个新文件)后,您将不得不重新加载该程序。这太复杂了,你很可能永远不会这样做。我认为你没有测试安全带。 Plunit for one,不包括此类测试。 根据经验:如果你无法在顶级测试谓词,那么你永远不会。改为考虑

  

minimum_with(Rel_2, Min, Els)

通过这种关系,您现在可以使用带有附加参数的通用xfoo/3,例如:

xfoo(o, A,B) :-
   foo(A,B).
xfoo(n, A,B) :-
   newfoo(A,B).

你自然会得到minimum_with(xfoo(X), Min, Els)的两个答案。您是否已使用findall/3代替setof/3您已经遇到严重问题。或者只是一般:minmum_with(\A^B^member(A-B, [x-10,y-20]), Min, Els)。因此,您可以在顶层玩游戏并生成许多有趣的测试用例。

未经检查的边框案例

您的第3版显然是我首选的方法,但仍有一些部分可以改进。特别是,如果有答案包含变量作为最小值。应该检查这些。

当然,setof/3也有其局限性。理想情况下,你会测试它们。答案不应包含约束,特别是不应包含相关变量。这表明setof/3本身有一定的限制。在开创性阶段之后,SICStus在这种情况下(20世纪90年代中期)产生了许多约束错误,后来改为忽略了无法处理它们的内置命令中的约束。另一方面,SWI在这里做了完全未定义的事情。有时事情被复制,有时不复制。举个例子: setof(A, ( A in 1..3 ; A in 3..5 ), _)setof(t, ( A in 1..3 ; A in 3.. 5 ), _)

通过包裹目标,可以避免这种情况。

call_unconstrained(Goal_0) :-
   call_residue_vars(Goal_0, Vs),
   ( Vs = [] -> true ; throw(error(representation_error(constraint),_)) ).

但请注意,SWI存在虚假限制:

?- call_residue_vars(all_different([]), Xs).
Xs = [_G1130].

在此期间不清楚这是否是一项功能。自从大约5年前引入call_residue_vars/2以来它一直存在。

答案 1 :(得分:1)

我不认为图书馆(汇总)涵盖了您的用例。 aggregate(min)允许一个见证:

  

min(Expr,Witness)       术语min(Min,Witness),其中Min是所有解决方案的Expr的最小版本,而Witness是应用于生成Min的解决方案的任何其他模板。如果多个解决方案提供相同的最小值,则Witness对应于第一个解决方案。

前段时间,我写了一个小型库',lag.pl,其中谓词以低开销聚合 - 因此名称(LAG = Linear AGgregate)。我添加了一个处理您的用例的代码段:

integrate(min_list_associated, Goal, Min-Ws) :-
    State = term(_, [], _),
    forall(call(Goal, V, W),    % W stands for witness
        (    arg(1, State, C),  % C is current min
             arg(2, State, CW), % CW are current min witnesses
             (   ( var(C) ; V @< C )
             ->  U = V, Ws = [W]
             ;   U = C,
                 (   C == V
                 ->  Ws = [W|CW]
                 ;   Ws = CW
                 )
             ),
             nb_setarg(1, State, U),
             nb_setarg(2, State, Ws)
        )),
    arg(1, State, Min), arg(2, State, Ws).

这是一个简单的集成扩展(min)... 比较方法肯定有问题(它使用较少的一般运算符来表示相等),可能值得采用像predsort / 3那样的常规调用。效率方面,更好的是将比较方法编码为&#39;函数选择器&#39;中的选项。 (在这种情况下min_list_associated)

编辑感谢@false和@Boris纠正相对于状态表示的错误。致电nb_setarg(2, State, Ws)实际上会更改“{1}}”一词。形状,使用State = (_,[],_)时。将相应地更新github仓库......

答案 2 :(得分:0)

使用library(pairs)和[sort/4],可以简单地写为:

?- bagof(B-A, foo(A, B), Ps),
   sort(1, @=<, Ps, Ss), % or keysort(Ps, Ss)
   group_pairs_by_key(Ss, [Min-As|_]).
Min = 2,
As = [b, e, h].

sort/4的此调用可以替换为keysort/2,但对于sort/4,也可以找到与最大的第二个参数关联的第一个参数:只使用{{1} }作为第二个参数。

这个解决方案可能不像其他解决方案那样节省时间和空间,但可能更容易理解。

但还有另一种方法可以完成:

@>=