zoukankan      html  css  js  c++  java
  • [Erlang0006][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

  • 相关阅读:
    简单读取网上的文本文件
    学习官方示例 System.RunError
    上周热点回顾(9.209.26)
    上周热点回顾(9.69.12)
    中国程序员节,10月24日,你同意吗?
    上周热点回顾(9.139.19)
    博客园电子期刊2010年8月刊发布啦
    [每周特惠]敏捷软件开发(原书第2版) +敏捷开发的艺术
    Meego专题上线了
    【投票】中国程序员节选在哪天
  • 原文地址:https://www.cnblogs.com/liangjingyang/p/2592691.html
Copyright © 2011-2022 走看看