鉴于数据库中存在以下事实:
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].
答案 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} }作为第二个参数。
这个解决方案可能不像其他解决方案那样节省时间和空间,但可能更容易理解。
但还有另一种方法可以完成:
@>=