在线文档教程

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/1iolist_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.