【问题标题】:Collect all "minimum" solutions from a predicate从谓词中收集所有“最小”解决方案
【发布时间】:2015-02-03 16:37:40
【问题描述】:

鉴于数据库中的以下事实:

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].

我可以使用aggregate/3,而不是setof/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].

注意

如果我正在寻找 数字 的最小值,这只会给出相同的结果。如果涉及算术表达式,结果可能会有所不同。如果涉及到非数字,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].

【问题讨论】:

  • “直接执行”是什么意思,C++ 是如何发挥作用的?
  • @ScottHunter “直接”意味着有一种方法可以调用 library(aggregate) 中的一个库谓词来执行此操作,但我太笨了,无法弄清楚。提到了 C++,因为链接算法的作用与我的 min_prefix/3 非常相似,但以更通用的方式。
  • @ScottHunter 查看我的编辑,了解提及 partition_point 背后的理由。
  • @DanielLyons:如果看到相应的 SQL 查询,我们会非常感兴趣。毕竟 SQL 查询通常 - 并不紧凑 - 但至少没有不必要的概念。我会为此悬赏。
  • @Boris:谢谢!顺便说一句,我有 2 个开放的赏金是很现实的(第三个只是为了感谢某人)。特别是明天结束的那个!!

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


【解决方案1】:

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

有没有办法简化问题?

以下许多评论可以添加到 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 不包括此类测试。 作为一个经验法则:如果你不能在顶层测试谓词,你永远不会。改为考虑

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 在这种情况下(1990 年代中期)产生了许多约束错误,后来改为忽略无法处理它们的内置约束。另一方面,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 以来,它就一直存在。

【讨论】:

    【解决方案2】:

    我不认为 library(aggregate) 涵盖了您的用例。 aggregate(min) 允许 一个 见证人:

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

    前段时间,我编写了一个小型“库”lag.pl,它使用低开销聚合谓词 - 因此得名 (LAG = Linear AGgregate)。我添加了一个处理您的用例的 sn-p:

    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).
    

    这是一个简单的integral(min)扩展... 比较方法肯定是有问题的(它使用不太通用的运算符来表示相等),可能值得采用像predsort/3 那样的传统调用。效率方面,最好将比较方法编码为“函数选择器”中的选项(在本例中为 min_list_associated)

    edit 感谢@false 和@Boris 纠正了与状态表示相关的错误。当使用State = (_,[],_) 时,调用nb_setarg(2, State, Ws) 实际上会改变术语的形状。将相应地更新 github repo...

    【讨论】:

    • 我不确定我能否真正告诉integrate/3 我的表达和见证是什么,或者我错过了什么? (我需要在call(Goal, V, W) 中切换VW 的顺序,否则它不会做我最初想要的?另外,它无意中颠倒了解决方案的顺序。)否则,是的,这是我正在努力实现的直接实施。我暗暗希望能找到基于现有工具的解决方案。
    • 是的,实际上我知道这些问题:但不清楚如何克服它们。 library(lambda) 它是迄今为止最好的,但它带来了一些效率问题。相反,我会遵循实用的、公认的愚蠢的路线来专门化“功能选择器”
    • 完全没问题;如果我愿意,我几乎可以将这个谓词的一个专门版本插入我的程序中。你是对的,但它还不是一个“图书馆”谓词。
    • State 只有两个参数有什么原因吗?它应该有三个。最后一个总是未实例化的变量
    【解决方案3】:

    使用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 还可以找到与最大的第二个参数相关联的第一个参数:只需使用@&gt;= 作为第二个参数。

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

    但还有另一种方法可以完全做到这一点:

    ?- bagof(A, ( foo(A, Min), \+ ( foo(_, Y), Y @< Min ) ), As).
    Min = 2,
    As = [b, e, h].
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 2020-12-20
      • 2023-03-19
      相关资源
      最近更新 更多