5.清单处理 | 5. List Handling
5清单处理
5.1创建一个列表
列表只能从末尾开始构建,并在开始时附加列表元素。如果你用“++
“运算符如下所示,将创建一个新列表,该列表是List1
,紧随其后List2
*
List1 ++ List2
看着lists:append/1
或++
将在普通的Erlang中实现,显然第一个列表是复制的:
append([H|T], Tail) ->
[H|append(T, Tail)];
append([], Tail) ->
Tail.
在递归和构建列表时,必须确保将新元素附加到列表的开头。这样,您将构建1
列表,而不是增长结果列表的数百或数千份副本。
让我们首先看看如何不这样做:
不要
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]).
这里建立了多个列表。在每一个迭代步骤中,都会创建一个新列表,它比新的前一个列表长一个元素。
为了避免在每次迭代中复制结果,请按反向顺序构建列表,并在完成时反转列表:
做
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曾经是缓慢的。
清单理解:
[Expr(E) || E <- List]
基本上被转换为一个本地函数:
'lc^0'([E|Tail], Expr) ->
[Expr(E)|'lc^0'(Tail, Expr)];
'lc^0'([], _Expr) -> [].
如果列表理解的结果显然
如果不使用,则不会构造列表。例如,在此代码中:
[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) -> [].
编译器还理解分配给%27[医]%27表示该值将不被使用。因此,以下示例中的代码也将得到优化:
_ = [io:put_chars(E) || E <- List],
ok.
5.3深度和平面列表
lists:flatten/1
构建一个全新的列表。因此它很贵,甚至再多点
比++
运算符%28复制其左参数,但不复制其右参数%29。
在以下情况下,您可以很容易地避免调用lists:flatten/1
*
- 将数据发送到端口时。端口理解深度列表,因此在将列表发送到端口之前,没有理由将其扁平化。
- 调用接受深列表的BIF时,如
list_to_binary/1
或iolist_to_binary/1
...
- 当您知道您的列表只有一级深度时,可以使用
lists:append/1
...
端口实例
做
...
port_command(Port, DeepList)
...
不要
...
port_command(Port, lists:flatten(DeepList))
...
向端口发送以零结尾的字符串的常见方法如下:
不要
...
TerminatedStr = String ++ [0], % String="foo" => [$f, $o, $o, 0]
port_command(Port, TerminatedStr)
...
相反:
做
...
TerminatedStr = [String, 0], % String="foo" => [[$f, $o, $o], 0]
port_command(Port, TerminatedStr)
...
附例
做
> lists:append([[1], [2], [3]]).
[1,2,3]
>
不要
> lists:flatten([[1], [2], [3]]).
[1,2,3]
>
5.4递归列表函数
在关于神话的一节中,揭露了以下神话:Tail-Recursive Functions are Much Faster Than Recursive Functions
...
体递归列表函数和尾递归函数之间通常没有太大的区别,后者在末尾反转列表。因此,集中精力编写漂亮的代码,忘记列表函数的性能。在代码%28的时间关键部分中,并且只有%29,测度
在重写代码之前。
注
本节是关于列表函数的构造
名单。不构造
列表的尾递归函数在常量空间中运行,而相应的体递归函数使用与列表长度成比例的堆栈空间。
例如,一个与整数列表相加的函数是不
应按以下方式写成:
不要
recursive_sum([H|T]) -> H+recursive_sum(T
recursive_sum([]) -> 0.
相反:
做
sum(L) -> sum(L, 0).
sum([H|T], Sum) -> sum(T, Sum + H
sum([], Sum) -> Sum.