Prolog递归的顺序是否重要?

时间:2015-06-24 08:04:58

标签: 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,List)
    ;  reduce(L,Ls,Y,[L|List])
    ).
reduce(_,[],Y,Y).

第二种解决方案不需要reverse/2

reduced([X|Xs],Result) :- 
    reduced(Xs,List),
    List=[A|_],
    (  A=X
    -> Result=List
    ;  Result=[X|List]
    ),
    !.
reduced(Result,Result).

在一系列语句之前或之后执行递归时有哪些优化注意事项?条件的顺序是否重要?我倾向于认为提前完成所有递归是要走的路,特别是因为这里需要向后构建列表。

4 个答案:

答案 0 :(得分:6)

优化任何内容时,请务必先测量! (我们大多数人都会忘记这一点....)

优化Prolog时,请注意以下事项:

  • 尾递归往往会做得更好(所以你的"之前或之后的一系列陈述"问题);
  • 避免创建您不需要的选择点(这取决于Prolog的实施)
  • 使用最佳算法(如果您不必,请不要遍历列表两次)。

优化的解决方案"对于或多或少的标准Prolog实现看起来可能会有所不同。我将其命名为list_uniq(类似于命令行uniq工具):

list_uniq([], []). % Base case
list_uniq([H|T], U) :-
    list_uniq_1(T, H, U). % Helper predicate

list_uniq_1([], X, [X]).
list_uniq_1([H|T], X, U) :-
    (   H == X
    ->  list_uniq_1(T, X, U)
    ;   [X|U1] = U,
        list_uniq_1(T, H, U1)
    ).

它与@CapelliC的reduce0/2不同,因为它使用滞后来避免第一个参数中[X|Xs][X,X|Xs]的固有非确定性

现在宣称它已经过优化":

  • 它只遍历列表一次(不需要反转)
  • 它尾递归
  • 它不会创建和丢弃选择点

您将获得与@CapelliC相同的12个推论,如果您使用稍长的列表,您将开始看到差异:

?- 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(list_uniq(L, U)).
% 600,006 inferences, 0.057 CPU in 0.057 seconds (100% CPU, 10499893 Lips)
As = [a, a, a, a, a, a, a, a, a|...],
Bs = [b, b, b, b, b, b, b, b, b|...],
Cs = [c, c, c, c, c, c, c, c, c|...],
L = [a, a, a, a, a, a, a, a, a|...],
U = [a, b, c, a, c, b].

与来自@ CapelliC的答案的reduce0reduce1reduce2相同的查询:

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

因此,创建和放弃带有削减(!)的选择点也是有代价的。

但是,list_uniq/2,对于第一个参数不是基础的查询,可能是错误的:

?- list_uniq([a,B], [a,b]).
B = b. % OK

?- list_uniq([a,A], [a]).
false. % WRONG!

reduce0/2reduce1/2也可能是错的:

?- reduce0([a,B], [a,b]).
false.

?- reduce1([a,B], [a,b]).
false.

关于reduce2/2,我不确定这个:

?- reduce2([a,A], [a,a]).
A = a.

相反,使用this answer中的if_/3定义:

list_uniq_d([], []). % Base case
list_uniq_d([H|T], U) :-
    list_uniq_d_1(T, H, U). % Helper predicate

list_uniq_d_1([], X, [X]).
list_uniq_d_1([H|T], X, U) :-
    if_(H = X,
        list_uniq_d_1(T, H, U),
        (   [X|U1] = U,
            list_uniq_d_1(T, H, U1)
        )
    ).

用它:

?- list_uniq_d([a,a,a,b], U).
U = [a, b].

?- list_uniq_d([a,a,a,b,b], U).
U = [a, b].

?- list_uniq_d([a,A], U).
A = a,
U = [a] ;
U = [a, A],
dif(A, a).

?- list_uniq_d([a,A], [a]).
A = a ;
false. % Dangling choice point

?- list_uniq_d([a,A], [a,a]).
false.

?- list_uniq_d([a,B], [a,b]).
B = b.

?- list_uniq_d([a,A], [a,a]).
false.

需要更长时间,但谓词似乎正确。 使用与其他时间相同的查询:

% 3,000,007 inferences, 1.140 CPU in 1.141 seconds (100% CPU, 2631644 Lips)

答案 1 :(得分:2)

分析似乎是回答效率问题的更简单方法:

% 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实施的解决方案策略,这是最重要的条件顺序。在天真的实现中,例如我的ILtail recursion optimization仅在递归调用是最后一个时被识别,而在前面被切割。只是为了确定它是确定性的......

答案 2 :(得分:2)

此答案是对@Boris's answer的直接跟进。

为了估计编译if_/3后我们可以预期的运行时间, 我创建list_uniq_e/2就像@Boris的list_uniq_d/2一样,手动编译if_/3

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_uniq_d/2

% 3,000,007 inferences, 0.623 CPU in 0.623 seconds (100% CPU, 4813150 Lips)

接下来,list_uniq_e/2

% 2,400,003 inferences, 0.132 CPU in 0.132 seconds (100% CPU, 18154530 Lips)

为了完整起见reduce0/2reduce1/2reduce2/2

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

还不错!并且......就优化if_/3而言,这不是行的结束:)

答案 3 :(得分:1)

希望这是@Boris's answerlast try更好的后续行动!

首先,这里是@鲍里斯的代码(100%原创):

// like canonical() but allows the last component of p to be a broken symlink
filesystem::path
resolve_most_symlinks(filesystem::path const& p, filesystem::path const& base = filesystem::current_path())
{
  if (is_symlink(p) && !exists(p))
    return canonical(absolute(p, base).remove_filename()) / p.filename();
  return canonical(p);
}

再增加一些基准测试代码:

list_uniq_d([], []). % Base case
list_uniq_d([H|T], U) :-
    list_uniq_d_1(T, H, U). % Helper predicate

list_uniq_d_1([], X, [X]).
list_uniq_d_1([H|T], X, U) :-
    if_(H = X,
        list_uniq_d_1(T, H, U),
        (   [X|U1] = U,
            list_uniq_d_1(T, H, U1)
        )
    ).

现在,让我们介绍模块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_if

现在...... *鼓声* ......看见:)

$ 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.
相关问题