我在GNU Prolog中有这个代码,我不知道为什么它对于50个元素的配对列表来说很慢:
pairwise_min( [X], X ) :- !.
pairwise_min( [(A,B)|T], (A,B) ) :-
pairwise_min( T, (_,B1) ),
B1 > B,
!.
pairwise_min( [(_,B)|T], (A1,B1) ) :-
pairwise_min( T, (A1,B1) ),
B1 =< B,
!.
以下查询需要10秒钟:
?- pairwise_min( [(25,25),(24,24),(23,23),(22,22),(21,21),(20,20),(19,19),(18,18),(17,17),(16,16),(15,15),(14,14),(13,13),(12,12),(11,11),(10,10),(9,9),(8,8),(7,7),(6,6),(5,5),(4,4),(3,3),(2,2),(1,1)], X ).
我可以做些什么来快速找到这个最低限度?
答案 0 :(得分:4)
您可以在跟踪时快速查看问题:
?- trace, pairwise_min( [(25,25),(24,24),(23,23),(22,22),(21,21),(20,20),(19,19),(18,18),(17,17),(16,16),(15,15),(14,14),(13,13),(12,12),(11,11),(10,10),(9,9),(8,8),(7,7),(6,6),(5,5),(4,4),(3,3),(2,2),(1,1)], X ).
Call: (7) pairwise_min([ (25, 25), (24, 24), (23, 23), (22, 22), (21, 21), (20, 20), (19, 19), (..., ...)|...], _G737) ?
... snip ...
Call: (30) pairwise_min([ (2, 2), (1, 1)], (_G1065, _G1066)) ?
Call: (31) pairwise_min([ (1, 1)], (_G1068, _G1069)) ?
Exit: (31) pairwise_min([ (1, 1)], (1, 1)) ?
Call: (31) 1>2 ?
Fail: (31) 1>2 ?
Redo: (30) pairwise_min([ (2, 2), (1, 1)], (_G1065, _G1066)) ?
Call: (31) pairwise_min([ (1, 1)], (_G1065, _G1066)) ?
Exit: (31) pairwise_min([ (1, 1)], (1, 1)) ?
Call: (31) 1=<2 ?
Exit: (31) 1=<2 ?
Exit: (30) pairwise_min([ (2, 2), (1, 1)], (1, 1)) ?
Call: (30) 1>3 ?
Fail: (30) 1>3 ?
Redo: (29) pairwise_min([ (3, 3), (2, 2), (1, 1)], (_G1062, _G1063)) ?
Call: (30) pairwise_min([ (2, 2), (1, 1)], (_G1062, _G1063)) ?
Call: (31) pairwise_min([ (1, 1)], (_G1068, _G1069)) ?
Exit: (31) pairwise_min([ (1, 1)], (1, 1)) ?
Call: (31) 1>2 ?
Fail: (31) 1>2 ?
Redo: (30) pairwise_min([ (2, 2), (1, 1)], (_G1062, _G1063)) ?
Call: (31) pairwise_min([ (1, 1)], (_G1062, _G1063)) ?
Exit: (31) pairwise_min([ (1, 1)], (1, 1)) ?
Call: (31) 1=<2 ?
Exit: (31) 1=<2 ?
基本上,问题是您最终会在两个案件中调用pairwise_min(T, ...)
,即使在第二种情况下它不会有所不同。尾部的成对最小值是尾部的成对最小值,无论当前元素如何检查它。
一个好的解决方案是通过使用显式条件消除第二条规则,如下所示:
pairwise_min( [X], X ) :- !.
pairwise_min( [(A,B)|T], (RA,RB) ) :-
pairwise_min(T, (A1,B1)), !,
(B1 > B -> (RA = A, RB = B)
; (RA = A1, RB = B1)).
对我来说可读性的一个重大障碍是,我并不真正意识到你试图通过成对最小化达到什么样的顺序。但是,无论如何将未来提取到自己的谓词中是一个好主意。我没有信心我已经得到了你的最低权利;是不是@</2
会做你想做的事情?如果是这种情况,你可以用非常紧凑的尾递归折叠来做到这一点:
minimum([X|Xs], Min) :- minimum(Xs, X, Min).
minimum([], Min, Min).
minimum([X|Xs], MinSoFar, Min) :-
(X @< MinSoFar -> minimum(Xs, X, Min)
; minimum(Xs, MinSoFar, Min)).
如果不是这种情况,您可以编写自己的谓词min_pair/2
来比较两对,并使用它而不是@</2
并获得好处。或者从上面修订的pairwise_min/2
中调用它,看看尾部调用优化的好处。
我认为你在改善这个版本的性能方面会遇到一些麻烦。
答案 1 :(得分:3)
从某种意义上说,你很幸运能找到合适的查询。想象一下,你会选择一个升序列表。在这种情况下,您目前会体验到更快的程序。但是在演示日,下降列表会破坏你的节目。
要了解实际发生的情况,仅查看程序中非常小的片段通常非常有用。由于你的削减基本上是削减绿色,我们可能会这样做,即使他们在场。我会在您的计划中添加 false
目标。并且不要指望这个片段有任何意义。它已经不复存在了。但是,它向我们展示了Prolog必须探索的搜索空间:
pairwise_min( [X], X ) :- false, !. pairwise_min( [(A,B)|T], (A,B) ) :- pairwise_min( T, (_,B1) ), falseB1 > B,!. pairwise_min( [(_,B)|T], (A1,B1) ) :- pairwise_min( T, (A1,B1) ), falseB1 =< B,!.
结果现在独立于具体值(假设第二个参数未实例化)。我们现在每个元素都有两个选择。使用长度为n的列表,我们有2个 n 选项。这就是造成这种开销的原因。要删除它,您必须更改可见部分中的某些内容。理想情况下,您在递归之前添加一些比较。
答案 2 :(得分:2)
一个非常简单的定义,未经优化但速度相当快
pairwise_min(L, (A,B)) :-
select((A,B), L, R),
\+ ( member((A1,B1), R), B1 < B ).
无论如何,研究代码的性能将是理解Prolog执行模型的一种非常好的方式