跳转到内容

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),它递归地反转子列表,但前提是它们包含整数。

华夏公益教科书