看到现在你也许会差异,为什么还没有介绍循环语句。作为一个B格很高的语言,erlang并不提供for/while等类似的循环控制关键字,而是使用递归的方式实现循环。递归的话,大家一定不陌生,简单地说就是调用自身函数实现循环,递归的实现有两个要素:
1. 一个基础条件(一般作为结束递归条件)
2. 一个调用自身的函数
以阶乘为例子:
fac(N) when N == 0 -> 1;
fac(N) when N > 0 -> N*fac(N-1).
这样就实现了一个递归,我们展开4的阶乘来看一看递归调用的过程:
fac(4) = 4*fac(4-1)
= 4*3*fac(3-1)
= 4*3*2*fac(2-1)
= 4*3*2*1*fac(0)
= 4*3*2*1*1
上面就完美了吗?NO!可以发现,当计算4的阶乘时,最大展开了5个参数。数据越大,计算时耗费内存也就越多。该是“尾递归“大显神威的时候啦!
为了避免大量的内存耗费,我们在入参中引入Accumlator作为保存计算结果临时变量,如下所示:
tail_fac(N) -> tail_fac(N,1).
tail_fac(0,Acc) -> Acc;
tail_fac(N,Acc) when N > 0 -> tail_fac(N-1,N*Acc).
问题就这么解决了?是的,看一下展开效果你就知道了。
tail_fac(4) = tail_fac(4,1)
tail_fac(4,1) = tail_fac(4-1, 4*1)
tail_fac(3,4) = tail_fac(3-1, 3*4)
tail_fac(2,12) = tail_fac(2-1, 2*12)
tail_fac(1,24) = tail_fac(1-1, 1*24)
tail_fac(0,24) = 24
在内存中至多只保存着两个项目,不会随着数据变化有增多。
由于递归是erlang中实现循环的唯一方式,为了让你能够更加得心应手的使用,这里会给出更多更加复杂的例子。
1.数据复制成数组:
duplicate(N, T) -> duplicate(N, T, []).
duplicate(0, _, Acc) -> Acc;
duplicate(N, T, Acc) -> duplicate(N - 1, T, [T|Acc]).
2.数组反转
reverse(T) when is_list(T) -> reverse(T, []).
reverse([], Acc) -> Acc;
reverse([H|T], Acc) -> reverse(T, [H|Acc]).
3.子数组(去数组前N个数据)
sublist(T, N) when is_list(T),length(T) >= N -> sublist(T,N,[]).
sublist(T, 0, Acc) -> Acc;
sublist([H|T], N, Acc) -> sublist(T, N-1, Acc ++ [H]).
4.相等大小的数组组合(zip)
zip(A1, A2) when is_list(A1), is_list(A2), length(A1)=:=length(A2)-> zip(A1, A2, []).
zip([],[], Acc) -> Acc;
zip([H1|T1], [H2|T2], Acc) -> zip(T1, T2, Acc++[{H1, H2}]).
5.快速排序
quicksort([]) -> [];
quicksort([Pivot|Rest]) ->
quicksort([Smaller|| Smaller <- Rest, Smaller =< Pivot])
++ [Pivot] ++
quicksort([Larger||Larger <- Rest, Larger > Pivot]).
最后,附上作者对递归的理解:
recursion coupled with pattern matching is sometimes an optimal solution to the problem of writing concise algorithms that are easy to understand. By subdividing each part of a problem into separate functions until they can no longer be simplified, the algorithm becomes nothing but assembling a bunch of correct answers coming from short rou- tines (that’s a bit similar to what we did with quicksort). This kind of mental abstraction is also possible with your everyday loops, but I believe the practice is easier with recursion. Your mileage may vary.