Optimization Prolog递归的顺序重要吗?
我有一个问题,关于一个问题的两种解决方案之间的区别。该问题要求将列表转换为截断列表,如下所示:Optimization Prolog递归的顺序重要吗?,optimization,memory-management,recursion,prolog,Optimization,Memory Management,Recursion,Prolog,我有一个问题,关于一个问题的两种解决方案之间的区别。该问题要求将列表转换为截断列表,如下所示: ?- reduce([a,a,a,b,b,c,c,b,b,d,d],Z). Z = [a,b,c,b,d]. 第一个解决方案需要一个额外的步骤来反转列表: reduce([X|Xs],Z) :- reduce(X,Xs,Y,[X]), reverse(Y,Z). reduce(X,[L|Ls],Y,List) :- ( X=L -> reduce(X,Ls,Y,
?- reduce([a,a,a,b,b,c,c,b,b,d,d],Z).
Z = [a,b,c,b,d].
第一个解决方案需要一个额外的步骤来反转列表:
reduce([X|Xs],Z) :-
reduce(X,Xs,Y,[X]),
reverse(Y,Z).
reduce(X,[L|Ls],Y,List) :-
( X=L
-> reduce(X,Ls,Y,List)
; reduce(L,Ls,Y,[L|List])
).
reduce(_,[],Y,Y).
第二种解决方案不需要反向/2:
reduced([X|Xs],Result) :-
reduced(Xs,List),
List=[A|_],
( A=X
-> Result=List
; Result=[X|List]
),
!.
reduced(Result,Result).
% 3,000,007 inferences, 0.623 CPU in 0.623 seconds (100% CPU, 4813150 Lips)
% 600,002 inferences, 0.079 CPU in 0.079 seconds (100% CPU, 7564981 Lips)
% 600,070 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 4266842 Lips)
% 600,001 inferences, 0.475 CPU in 0.475 seconds (100% CPU, 1262018 Lips)
在一系列语句之前或之后执行递归时,需要考虑哪些优化因素?条件的顺序重要吗?我倾向于认为,提前完成所有递归是可行的,特别是因为在这里向后构建列表是必要的。当您优化任何内容时,请确保首先进行度量!(我们大多数人都会忘记这一点……) 优化Prolog时,请注意以下事项:
- 尾部递归往往做得更好(所以你的“一系列语句之前或之后”的问题就出现了)李>
- 避免创建不需要的选择点(这取决于Prolog实现)
- 使用最佳算法(如中所示,如果不需要,不要遍历列表两次)
list\u uniq
(类似于命令行uniq
工具):
与@capelical回答中的reduce0
,reduce1
,reduce2
相同的查询:
% reduce0(L, U)
% 600,001 inferences, 0.125 CPU in 0.125 seconds (100% CPU, 4813955 Lips)
% reduce1(L, U)
% 1,200,012 inferences, 0.393 CPU in 0.394 seconds (100% CPU, 3050034 Lips)
% reduce2(L, U)
% 2,400,004 inferences, 0.859 CPU in 0.861 seconds (100% CPU, 2792792 Lips)
reduce0/2
和reduce1/2
也可能是错误的:
?- reduce0([a,B], [a,b]).
false.
?- reduce1([a,B], [a,b]).
false.
相反,使用来自以下位置的的定义:
分析似乎是回答效率问题的更简单方法:
% my own
reduce0([], []).
reduce0([X,X|Xs], Ys) :- !, reduce0([X|Xs], Ys).
reduce0([X|Xs], [X|Ys]) :- reduce0(Xs, Ys).
% your first
reduce1([X|Xs],Z) :- reduce1(X,Xs,Y,[X]), reverse(Y,Z).
reduce1(X,[L|Ls],Y,List) :-
X=L -> reduce1(X,Ls,Y,List);
reduce1(L,Ls,Y,[L|List]).
reduce1(_,[],Y,Y).
% your second
reduce2([X|Xs],Result) :-
reduce2(Xs,List),
List=[A|_],
(A=X -> Result=List;
Result=[X|List]),!.
reduce2(Result,Result).
SWI Prolog提供时间/1:
4 ?- time(reduce0([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 12 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 340416 Lips)
Z = [a, b, c, b, d].
5 ?- time(reduce1([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 19 inferences, 0.000 CPU in 0.000 seconds (90% CPU, 283113 Lips)
Z = [a, b, c, b, d] ;
% 5 inferences, 0.000 CPU in 0.000 seconds (89% CPU, 102948 Lips)
false.
6 ?- time(reduce2([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 12 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 337316 Lips)
Z = [a, b, c, b, d].
您的第二个谓词的执行方式与我的类似,而第一个谓词似乎留下了一个选择点
鉴于Prolog实现的解析策略,条件的顺序是最重要的。在像我这样的幼稚实现中,只有当递归调用是最后一个调用时,并且前面有一个cut时,才会被识别。只是为了确定它是确定性的…这个答案是对这个问题的直接跟进
为了估计运行时间,如果编译了u3
,我们可以期望一次,
我制作了list\u uniq\u e/2
,就像@Boris的list\u uniq\u d/2
一样,用if\u3
手工编译
list_uniq_e([], []). % Base case
list_uniq_e([H|T], U) :-
list_uniq_e_1(T, H, U). % Helper predicate
list_uniq_e_1([], X, [X]).
list_uniq_e_1([H|T], X, U) :-
=(H,X,Truth),
list_uniq_e_2(Truth,H,T,X,U).
list_uniq_e_2(true ,H,T,_, U ) :- list_uniq_e_1(T,H,U).
list_uniq_e_2(false,H,T,X,[X|U]) :- list_uniq_e_1(T,H,U).
让我们比较一下运行时(SWI Prolog 7.3.1,Intel Core i7-4700MQ 2.4GHz)
首先,list\u uniq\u d/2
:
reduced([X|Xs],Result) :-
reduced(Xs,List),
List=[A|_],
( A=X
-> Result=List
; Result=[X|List]
),
!.
reduced(Result,Result).
% 3,000,007 inferences, 0.623 CPU in 0.623 seconds (100% CPU, 4813150 Lips)
% 600,002 inferences, 0.079 CPU in 0.079 seconds (100% CPU, 7564981 Lips)
% 600,070 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 4266842 Lips)
% 600,001 inferences, 0.475 CPU in 0.475 seconds (100% CPU, 1262018 Lips)
接下来,list\u uniq\u e/2
:
% 2,400,003 inferences, 0.132 CPU in 0.132 seconds (100% CPU, 18154530 Lips)
为完整起见,reduce0/2
、reduce1/2
和reduce2/2
:
reduced([X|Xs],Result) :-
reduced(Xs,List),
List=[A|_],
( A=X
-> Result=List
; Result=[X|List]
),
!.
reduced(Result,Result).
% 3,000,007 inferences, 0.623 CPU in 0.623 seconds (100% CPU, 4813150 Lips)
% 600,002 inferences, 0.079 CPU in 0.079 seconds (100% CPU, 7564981 Lips)
% 600,070 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 4266842 Lips)
% 600,001 inferences, 0.475 CPU in 0.475 seconds (100% CPU, 1262018 Lips)
不错!而且。。。这还没有结束——就优化而言(如果涉及到:)希望这是一个比我更好的后续行动
首先,这里是@Boris的代码(100%原创):
再加上一些用于基准测试的代码:
bench(P_2) :-
length(As, 100000), maplist(=(a), As),
length(Bs, 100000), maplist(=(b), Bs),
length(Cs, 100000), maplist(=(c), Cs),
append([As, Bs, Cs, As, Cs, Bs], L),
time(call(P_2,L,_)).
现在,让我们介绍一下模块re\u if
:
:- module(re_if, [if_/3, (=)/3, expand_if_goals/0]).
:- dynamic expand_if_goals/0.
trusted_truth(_=_). % we need not check truth values returned by (=)/3
=(X, Y, R) :- X == Y, !, R = true.
=(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different
=(X, Y, R) :- X \= Y, !, R = false. % semantically different
=(X, Y, R) :- R == true, !, X = Y.
=(X, X, true).
=(X, Y, false) :- dif(X, Y).
:- meta_predicate if_(1,0,0).
if_(C_1,Then_0,Else_0) :-
call(C_1,Truth),
functor(Truth,_,0), % safety check
( Truth == true -> Then_0
; Truth == false , Else_0
).
:- multifile system:goal_expansion/2.
system:goal_expansion(if_(C_1,Then_0,Else_0), IF) :-
expand_if_goals,
callable(C_1), % nonvar && (atom || compound)
!,
C_1 =.. Ps0,
append(Ps0,[T],Ps1),
C_0 =.. Ps1,
( trusted_truth(C_1)
-> IF = (C_0, ( T == true -> Then_0 ; Else_0))
; IF = (C_0,functor(T,_,0),( T == true -> Then_0 ; T == false, Else_0))
).
现在*drumroll*。。。瞧:)
$swipl
欢迎使用SWI Prolog(多线程,64位,版本7.3.3-18-gc341872)
版权所有(C)1990年至2015年阿姆斯特丹大学,阿姆斯特丹
SWI Prolog绝对没有保修。这是自由软件,
欢迎您在特定条件下重新分发。
请访问http://www.swi-prolog.org 详情请参阅。
要获得帮助,请使用?-帮助(主题)。或者?-恰到好处(词)。
?编译(如果重新编译),编译(列出uniq)。
对。
-工作台(列表)。
%2400010个推断,0.865秒内0.865 CPU(100%CPU,2775147个嘴唇)
对。
?-断言(re_if:expand_if_goals),编译(list_uniq)。
对。
-工作台(列表)。
%1200005推论,0.215秒内0.215 CPU(100%CPU,5591612个嘴唇)
对。
reduce([a,B],[a,B])
失败-有效与否-这完全是不正确的。我的意思是,list\u uniq([a,a],[a]),a=a
。A
太有启发性了:-)@WillNess:你对模块名有什么建议吗?@WillNess:只是一个吸引人的模块名<代码>具体化的真相
有点长。我想到了reif
(在德语中是成熟的意思,作为一个“有趣的”双关语)。@false“reif”是好的。它还分为两个部分,“re:if”,因此很有启发性。好名声。它可以是“re/if”吗?斜杠可以吗?@WillNess:可以!非常感谢。这真是太棒了。使用前置条件的要点是,在更一般的情况下,它允许我们根据谓词参数上已经存在的当前约束使用不同的算法。通过使用属性变量,我们可以将约束“秘密地”传播到任何关心检查约束的谓词。您和@false最近显示的所有*\d
谓词都可以粘在现有的“标准”谓词上,基本上实现零成本。我在前面的评论中所说的“约束”是指例如ground(X)
,is_list(X)
,sort(X,X)
,等等,member\u d
ismember
ismember\u chk
isord\u memberchk
if前提条件(排序(X,X))
;这也消除了选择点@鲍里斯。听起来不错,但我还不确定。我对“属性变量”的想法持怀疑态度。。。也许我不明白,但是一旦var变为非var,你就会失去属性,对吗?现在正在阅读SWI Prolog手册。如果我真的实现了一个有效的解决方案,我将尝试回答扩展版本和未扩展版本的行为不同。与SWI扩展的one/1
相同的错误。
bench(P_2) :-
length(As, 100000), maplist(=(a), As),
length(Bs, 100000), maplist(=(b), Bs),
length(Cs, 100000), maplist(=(c), Cs),
append([As, Bs, Cs, As, Cs, Bs], L),
time(call(P_2,L,_)).
:- module(re_if, [if_/3, (=)/3, expand_if_goals/0]).
:- dynamic expand_if_goals/0.
trusted_truth(_=_). % we need not check truth values returned by (=)/3
=(X, Y, R) :- X == Y, !, R = true.
=(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different
=(X, Y, R) :- X \= Y, !, R = false. % semantically different
=(X, Y, R) :- R == true, !, X = Y.
=(X, X, true).
=(X, Y, false) :- dif(X, Y).
:- meta_predicate if_(1,0,0).
if_(C_1,Then_0,Else_0) :-
call(C_1,Truth),
functor(Truth,_,0), % safety check
( Truth == true -> Then_0
; Truth == false , Else_0
).
:- multifile system:goal_expansion/2.
system:goal_expansion(if_(C_1,Then_0,Else_0), IF) :-
expand_if_goals,
callable(C_1), % nonvar && (atom || compound)
!,
C_1 =.. Ps0,
append(Ps0,[T],Ps1),
C_0 =.. Ps1,
( trusted_truth(C_1)
-> IF = (C_0, ( T == true -> Then_0 ; Else_0))
; IF = (C_0,functor(T,_,0),( T == true -> Then_0 ; T == false, Else_0))
).
$ swipl
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.3-18-gc341872)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.
For help, use ?- help(Topic). or ?- apropos(Word).
?- compile(re_if), compile(list_uniq).
true.
?- bench(list_uniq_d).
% 2,400,010 inferences, 0.865 CPU in 0.865 seconds (100% CPU, 2775147 Lips)
true.
?- assert(re_if:expand_if_goals), compile(list_uniq).
true.
?- bench(list_uniq_d).
% 1,200,005 inferences, 0.215 CPU in 0.215 seconds (100% CPU, 5591612 Lips)
true.