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