[Erlang-0006][OTP] 高效指南 -- 列表解析
原文链接:http://www.erlang.org/doc/efficiency_guide/listHandling.html
水平有限,错误之处欢迎指正。
5 列表解析
5.1 创建一个列表
创建列表最好从最后开始,一个元素接一个元素地附加在前面。如果你用++操作符:
List1 ++ List2
会通过把List1拷贝一份附加在List2前面来创建一个新的列表。看一下lists:append/1或者++在Erlang里是如何实现的,我们可以清楚地看到第一个列表被拷贝。
append([H|T], Tail) -> [H|append(T, Tail)]; append([], Tail) -> Tail.
所以当递归或者创建列表时要注意的是,确保把元素放到列表的前面,以便你创建列表时,随着列表的生成,不会有成百上千的拷贝。
先让我们看看不鼓励的做法:
bad_fib(N) -> bad_fib(N, 0, 1, []). bad_fib(0, _Current, _Next, Fibs) -> Fibs; bad_fib(N, Current, Next, Fibs) -> bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).
这里我不是创建了一个列表,每次迭代我们创建了一个新的列表,只比上一个多一个元素。
避免每次迭代都要拷贝结果,我们必须反序创建列表,在最后反转之:
DO
tail_recursive_fib(N) -> tail_recursive_fib(N, 0, 1, []). tail_recursive_fib(0, _Current, _Next, Fibs) -> lists:reverse(Fibs); tail_recursive_fib(N, Current, Next, Fibs) -> tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).
5.2 列表解析
列表解析依然被误认为很慢。它们过去是用funs实现的,funs之前很慢。
在当前Erlang/OTP版本(包括R12B),列表解析
[Expr(E) || E <- List]
基本上被解析成一个本地函数
'lc^0'([E|Tail], Expr) -> [Expr(E)|'lc^0'(Tail, Expr)]; 'lc^0'([], _Expr) -> [].
在R12B中,如果列表解析的结果明显地不会被用到,列表根本不会被构建。例如下面的代码
[io:put_chars(E) || E <- List],
ok.
或者这样的代码
. . . case Var of ... -> [io:put_chars(E) || E <- List]; ... -> end, some_function(...), . . .
结果既不付给变量又不传给另一个函数,也不是返回值,那么就没有必要构建列表,编译器会简化这个列表解析成
'lc^0'([E|Tail], Expr) -> Expr(E), 'lc^0'(Tail, Expr); 'lc^0'([], _Expr) -> [].
5.3 嵌套和拉伸列表
lists:flatten/1创建一个全新的列表,因此,代价比较高,甚至比++还要高(++只会拷贝左边的列表,右边的不拷贝)。
以下情况可以避免使用lists:flatten/1:
a. 向端口发送数据。端口能够处理嵌套列表,所以不须在发送前拉平列表。
b. 调用BIFs接收嵌套列表,例如list_to_binary/1或iolist_to_binary/1。
c. 当你的列表只有一层嵌套的时候,可以用lists:append/1。
Port example
DO
...
port_command(Port, DeepList)
...
DO NOT
...
port_command(Port, lists:flatten(DeepList))
...
通常会这样向端口发送一个以0为结尾的字符串:
DO NOT
... TerminatedStr = String ++ [0], % String="foo" => [$f, $o, $o, 0] port_command(Port, TerminatedStr) ...
可以用这种方式来代替:
DO
... TerminatedStr = [String, 0], % String="foo" => [[$f, $o, $o], 0] port_command(Port, TerminatedStr) ...
Append example
DO
> lists:append([[1], [2], [3]]). [1,2,3] >
DO NOT
> lists:flatten([[1], [2], [3]]). [1,2,3] >
5.4 为什么不必担心通过列表来递归的函数
在性能谬论那一章,下面这条谎言被揭穿:尾递归函数比递归函数快很多。
总的来说,在R12B里通常一个列表递归函数和尾递归加反转没有太大差别。因此,大多数情况下应该忽略列表函数的性能,重点关注代码的整洁。只有在运行时间要求严格的那一小段代码需要特殊照顾,并且在重写它们之前一定要测试。
重要提示:这一节谈论的列表函数都会构建列表。尾递归函数只需要恒定的空间运行,不用构建新的列表,而递归函数用到的栈空间和列表的长度成正比。例如,求和一个整数列表的函数不应该这样写
DO NOT
recursive_sum([H|T]) -> H+recursive_sum(T);
recursive_sum([]) -> 0.
而应该
DO
sum(L) -> sum(L, 0). sum([H|T], Sum) -> sum(T, Sum + H); sum([], Sum) -> Sum.
原创翻译,欢迎任何形式的转载,但请务必注明出处:http://www.cnblogs.com/liangjingyang