跳轉到內容

Erlang 程式設計/遞迴技巧

來自華夏公益教科書,開放的書籍,開放的世界

遞迴技巧

[編輯 | 編輯原始碼]

簡單技巧

[編輯 | 編輯原始碼]

組裝-拆卸

[編輯 | 編輯原始碼]

我們經常希望使用遞迴函式構建列表。也許我們想反轉一個列表。我們從單引數版本(公共入口點函式)開始,並使用它來呼叫雙引數版本(私有),其中額外的引數包含我們想要構建的輸出。我們可以從輸入中取出頭部,[H|T],在我們構建輸出的同時,構建。當輸入為空時,我們完成了。

觀察字串實際上是如何作為字元列表處理的,這很不錯。語法糖將字串轉換為列表並返回。

(這裡我們使用遞迴的組裝-拆卸技巧。Johnny-Five 不會贊成這種技巧。[參考 1986 年電影“短路”。])

記住,我們需要用括號括住 H,因為它需要在使用加號加號“++”進行列表連線時是一個列表。T 在 [H|T] 中始終是一個列表。H 在 [H|T] 中可能是也可能不是一個列表;無論哪種方式,我們都需要給它一個括號,使其正常工作。

%%% provides utility functions
                                 %
-module(util).                   
-export([reverse/1]).
                                 %% reverse(L) is for public use
                                 %%   RETURNS: the reverse of a list L
                                 %%   ARG: L <== any list 
reverse( L ) ->                  %% The entry point function: reverse(L) 
    reverse( L, []).             %%   the private function: reverse(L1, L2)
                                 %%
                                 %% reverse() uses the assembly-disassembly method
                                 %%   of recursion.
                                 %% reverse(L1,L2) is the private version of reverse
reverse([], Build) ->            %% The base case: has an empty list for input so
    Build;                       %%   we're done building and return the final answer: Build
reverse([H|T], Build) ->         %% The recursive case: has at least one element: H 
    reverse( T, [H] ++ Build ).  %%   so glue it onto Build and 
                                 %%   recurse with the left-overs: T
% sample output
                                                                      %
c(util).            
  {ok,util}
                                                                      %
util:reverse("abc").
  "cba"
                                                                      %
util:reverse([1,2,3]).
  [3,2,1]
                                                                      %
util:reverse( [ [1,1], [2,2], [3,3]]).
  [ [3,3], [2,2], [1,1]]
                                                                      %
util:reverse([ {1,1}, {2,2}, {3,3} ]).
  [ {3,3}, {2,2}, {1,1} ]
                                                                      %
util:reverse( { [1,1], [2,2], [3,3] } ).
  error

編譯並執行。

我們可以反轉字串,它實際上是一個列表。我們可以反轉一個整數列表。我們可以反轉一個列表列表。我們可以反轉一個元組列表。但我們不能反轉一個元組列表,因為頂層結構不是列表。

如果我們想變得聰明一些,我們可以在入口點使用一個守衛來檢查引數的型別,然後如果它是一個元組,將其更改為一個列表,執行反轉,然後在輸出時將其更改回一個元組。

請注意,這個例子純粹是教育性的,在實際應用中,您應該避免一次將一個元素追加到列表中。構建列表的正確方法是使用 [Head|Tail] 形式。這通常會導致列表以相反的順序出現。為了解決這個問題,我們隨後使用 lists:reverse 內建函式。參見示例

%slow
build_append()->build_append(0,[]).
build_append(N,L) when N < 100000 -> build_append(N+1,L++[N]);  % function calls its self with N+1 and a new list with N as the last element.
build_append(_,L) -> L.
%fast
build_insert()->build_insert(0,[]).
build_insert(N,L) when N < 100000 -> build_insert(N+1,[N|L]);   % function calls its self with N+1 and a new list with N as the head.
build_insert(_,L) -> lists:reverse(L).                          % function has to reverse the list before it retuns it.

編譯並執行這些函式進行比較。

速度差異背後的原因在於 Erlang 列表作為連結串列的實現。

1) 編寫一個函式 t_reverse(T),它反轉一個大小為 5 的元組 T。(即 {a,b,c,d,e})

2) 編寫一個函式 r_reverse(L),它遞迴地反轉列表列表 L 中的每個子列表。

3) 編寫一個函式 s_reverse(L),它遞迴地反轉子列表,但不反轉列表列表的頂層。

4) 編寫一個函式 n_reverse(L),它遞迴地反轉子列表,但前提是它們包含整數。

華夏公益教科書