首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

5.清单处理 | 5. List Handling

5.1创建一个列表

列表只能从末尾开始构建,并在开始时附加列表元素。如果你用“++“运算符如下所示,将创建一个新列表,该列表是List1,紧随其后List2*

代码语言:javascript
复制
List1 ++ List2

看着lists:append/1++将在普通的Erlang中实现,显然第一个列表是复制的:

代码语言:javascript
复制
append([H|T], Tail) ->
    [H|append(T, Tail)];
append([], Tail) ->
    Tail.

在递归和构建列表时,必须确保将新元素附加到列表的开头。这样,您将构建1列表,而不是增长结果列表的数百或数千份副本。

让我们首先看看如何不这样做:

不要

代码语言:javascript
复制
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]).

这里建立了多个列表。在每一个迭代步骤中,都会创建一个新列表,它比新的前一个列表长一个元素。

为了避免在每次迭代中复制结果,请按反向顺序构建列表,并在完成时反转列表:

代码语言:javascript
复制
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曾经是缓慢的。

清单理解:

代码语言:javascript
复制
[Expr(E) || E <- List]

基本上被转换为一个本地函数:

代码语言:javascript
复制
'lc^0'([E|Tail], Expr) ->
    [Expr(E)|'lc^0'(Tail, Expr)];
'lc^0'([], _Expr) -> [].

如果列表理解的结果显然如果不使用,则不会构造列表。例如,在此代码中:

代码语言:javascript
复制
[io:put_chars(E) || E <- List],
ok.

或者在这个代码中:

代码语言:javascript
复制
...
case Var of
    ... ->
        [io:put_chars(E) || E <- List];
    ... ->
end,
some_function(...),
...

该值没有赋值给变量,也没有传递给其他函数,也没有返回。这意味着不需要构造列表,编译器将简化列表理解的代码,以便:

代码语言:javascript
复制
'lc^0'([E|Tail], Expr) ->
    Expr(E),
    'lc^0'(Tail, Expr);
'lc^0'([], _Expr) -> [].

编译器还理解分配给%27[医]%27表示该值将不被使用。因此,以下示例中的代码也将得到优化:

代码语言:javascript
复制
_ = [io:put_chars(E) || E <- List],
ok.

5.3深度和平面列表

lists:flatten/1构建一个全新的列表。因此它很贵,甚至再多点++运算符%28复制其左参数,但不复制其右参数%29。

在以下情况下,您可以很容易地避免调用lists:flatten/1*

  • 将数据发送到端口时。端口理解深度列表,因此在将列表发送到端口之前,没有理由将其扁平化。
  • 调用接受深列表的BIF时,如list_to_binary/1iolist_to_binary/1...
  • 当您知道您的列表只有一级深度时,可以使用lists:append/1...

端口实例

代码语言:javascript
复制
...
port_command(Port, DeepList)
...

不要

代码语言:javascript
复制
...
port_command(Port, lists:flatten(DeepList))
...

向端口发送以零结尾的字符串的常见方法如下:

不要

代码语言:javascript
复制
...
TerminatedStr = String ++ [0], % String="foo" => [$f, $o, $o, 0]
port_command(Port, TerminatedStr)
...

相反:

代码语言:javascript
复制
...
TerminatedStr = [String, 0], % String="foo" => [[$f, $o, $o], 0]
port_command(Port, TerminatedStr) 
...

附例

代码语言:javascript
复制
> lists:append([[1], [2], [3]]).
[1,2,3]
>

不要

代码语言:javascript
复制
> lists:flatten([[1], [2], [3]]).
[1,2,3]
>

5.4递归列表函数

在关于神话的一节中,揭露了以下神话:Tail-Recursive Functions are Much Faster Than Recursive Functions...

体递归列表函数和尾递归函数之间通常没有太大的区别,后者在末尾反转列表。因此,集中精力编写漂亮的代码,忘记列表函数的性能。在代码%28的时间关键部分中,并且只有%29,测度在重写代码之前。

本节是关于列表函数的构造名单。不构造列表的尾递归函数在常量空间中运行,而相应的体递归函数使用与列表长度成比例的堆栈空间。

例如,一个与整数列表相加的函数是应按以下方式写成:

不要

代码语言:javascript
复制
recursive_sum([H|T]) -> H+recursive_sum(T);
recursive_sum([])    -> 0.

相反:

代码语言:javascript
复制
sum(L) -> sum(L, 0).

sum([H|T], Sum) -> sum(T, Sum + H);
sum([], Sum)    -> Sum.

扫码关注腾讯云开发者

领取腾讯云代金券