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