如何在prolog中实现展平列表,尾部递归?

时间:2014-06-26 09:51:12

标签: prolog tail-recursion

如何在prolog中实现展平列表,尾部递归?

这是flatten / 2的代码,带有简单的递归(这意味着没有反向跟踪):

flatten([], []).
flatten([L|Ls], FlatL) :-
    !,
    flatten(L, NewL),
    flatten(Ls, NewLs),
    append(NewL, NewLs, FlatL).
flatten(L, [L]).

?- flatten([1, [2,3], [4]], X).
X=[1,2,3,4].

我尝试使用尾递归(Accumulator)进行相同的算法。例如,谓词sum/2返回列表中所有成员的添加,并带有回溯:

sum([X],[X]).
sum([H|T],S) :- sum(T,S1), S is H + S1 .

与尾递归相同的算法是

sum1(L,S) :- sum1(L,0,S).

sum1([],Acc,Acc).
sum1([H|T],Acc,S) :- Acc1 is Acc+H, s(T,Acc1,S).

1 个答案:

答案 0 :(得分:3)

您可能希望阅读尾递归优化

[Inconceivable] You keep using that work. I do not think it means what you think it means.

尾递归优化/消除与累加器几乎没有任何关系。它与调用堆栈中的当前帧是否可以重用有关。如果可以,则递归调用有效地转换为迭代。如果无法重用,则必须在堆栈上推送一个新的框架,其副作用很大,最终会导致堆栈溢出异常。

这是尾递归并且被优化为迭代:

write_list( [] ) .
write_list( [X|Xs] ) :-
  write(X),nl,
  write_list(Xs).

这不是:

factorial(1,1) .
factorial(N,F) :-
  N > 1 ,
  N1 is N-1 ,
  factorial(N1,F1) ,
  F is N1+F1 .

不同之处在于,在前者中,在递归调用之后不会使用任何本地的东西,因此可以重用堆栈帧。在后者中,必须保留堆栈帧的内容,因此必须为递归调用分配新的堆栈帧。

但是,以下内容应该为您完成这项工作。

flatten( Xs , Fs ) :-       % to flatten a list of via an accumulator...
  flatten( Xs , [] , Rs ) , % - invoke the worker predicate with the accumulator seeded as the empty list.
  reverse(Rs,Fs)            % - since the flattened list will be built in reverse order, you'll need to reverse it after all the work is done.
  .

flatten( []     , Fs , Fs ) .  % if the source list is exhausted, our work is done.
flatten( [X|Xs] , Ts , Fs ) :- % otherwise...
  is_list(X) ,                 % - if the head of the list is itself a list
  ! ,                          % - eliminate the choice point.
  flatten(X,Ts,T1) ,           % - flatten the sublist by invoking the worker predicate on the sublist
  flatten(Xs,T1,Fs)            % - and then continue
  .                            %
flatten( [X|Xs] , Ts , Fs ) :- % finally, the list head must be unbound or some other non-list thing.
  flatten(Xs,[X|Ts],Fs)        % - prepend it to the accumulator and continue.
  .                            %

is_list( X     ) :- var(X) , ! , fail . % unbound variables are manifestly not lists.
is_list( []    ) .                      % but the empty lislt is.
is_list( [_|_] ).                       % and so is a non-empty list.

你应该注意到它不是完全尾递归的。每次遇到嵌套列表时,都必须保存当前状态,因此它可以在递归调用之后从它停止的位置继续,以展平子列表。