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实现)
  • 使用最佳算法(如中所示,如果不需要,不要遍历列表两次)
为或多或少的标准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
is
member
is
member\u chk
is
ord\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.