erlang list的使用与优化建议

erlang有两种复合结构,tuple和list,两者的区别是tuple子元素的个数是固定不变的,声明后就不能改变了;而list是可变的,可以通过[H|T]来取出或插入新元素。本篇讲erlang list方面的知识,主要说一些基本操作和常用的list函数,再讲一些可以优化的点。

list基本操作

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 1> A = [1, 2, 3, "abc"].  
  2. [1,2,3,"abc"]  
  3. 2> length(A).  
  4. 4  
  5. 3> is_list(A).  
  6. true  
  7. 4> list_to_binary(A).  
  8. <<1,2,3,97,98,99>>  
  9. 5> list_to_bitstring(A).  
  10. <<1,2,3,97,98,99>>  
  11.   
  12.   
  13. 6> [A1|A2] = A.  
  14. [1,2,3,"abc"]  
  15. 7> A1.  
  16. 1  
  17. 8> A2.  
  18. [2,3,"abc"]  
  19. 9> hd(A).  
  20. 1  
  21. 10> tl(A).  
  22. [2,3,"abc"]  
  23. 11> B = [0|A].  
  24. [0,1,2,3,"abc"]  

 

list函数说明

常用的lists函数

这里介绍一些常用的lists函数,其他看erlang lists文档

lists:foreach(Fun, List) -> ok

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 1> lists:foreach(fun(X) -> io:format("~p~n", [X]) end, [1,2,3]).  
  2. 1  
  3. 2  
  4. 3  
  5. ok  

例子中,通过遍历列表[1,2,3],把取出的元素给X,直到遍历结束,最后返回ok

 

lists:foldl(Fun, Acc0, List) -> Acc1

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 2> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).  
  2. 15  

例子中,0指定了Sum的初值,通过遍历列表[1,2,3,4,5],把取出的元素给X,再计算X+Sum,结果带入下次遍历传给 Sum,直到最后一次遍历将X+Sum的结果作为返回值

下面是lists:foldl/3 的实现,实际上是一个尾递归函数

foldl(F, Accu, [Hd|Tail]) ->
    foldl(F, F(Hd, Accu), Tail);
foldl(F, Accu, []) when is_function(F, 2) ->

    Accu.

 

lists:reverse(List1) -> List2

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 3> lists:reverse([1,2,3,4,5]).  
  2. [5,4,3,2,1]  

list反转(这是个bif函数,效率较高)

 

lists:flatten(DeepList) -> List

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 4> lists:flatten([1,[2,3],[4,[5]],6]).  
  2. [1,2,3,4,5,6]  

 

lists扁平化,将有深度结构的list转成一个简单的list。这个函数有性能开销,见文章后面说明。

 

lists:member(Elem, List) -> boolean()

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 5> lists:member(3,[1,2,3]).  
  2. true  

检查元素是否存在list中(这是个bif函数,效率较高)

 

lists:sort(List1) -> List2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 6> lists:sort([2,3,1]).  
  2. [1,2,3]  

lists元素排序,排序从小到大,如果想自定义排序规则,可以使用lists:sort/2

 

lists:sort(Fun, List1) -> List2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 7> lists:sort(fun(A,B)-> A>=B end,[2,3,1]).  
  2. [3,2,1]  

 

 

处理TupleList的lists函数

TupleList是元素是tuple的list,erlang提供了一些接口专门处理这类数据。

lists:keymember(Key, N, TupleList) -> boolean()

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 8> lists:keymember(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. true  

检查list中是否有第N位置为Key的tuple(这是个bif函数,效率较高)

 


lists:keyfind(Key, N, TupleList) -> Tuple | false

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 9> lists:keyfind(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. {b,1,1}  

返回list中第N位置为Key的tuple,找不到返回false

 

注:

 1. 找到第一个匹配的tuple就返回,找不到返回false

 2. 这个接口可以替代lists:keysearch(Key, N, TupleList) 使用

 3. 这是个bif函数,效率较高

lists:keysearch(Key, N, TupleList)的官方文档中有这么一句:

This function is retained for backward compatibility. The function lists:keyfind/3 (introduced in R13A) is in most cases more convenient.

意思就是说lists:keysearch/3只是为了保持向后兼容,使用lists:keyfind/3会更方便


lists:keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2} | false

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 10> lists:keytake(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. {value,{b,1,1},[{a,1,1},{c,1,1}]}  

返回list中第N位置为Key的tuple,并返回找到的tuple和剩下的TupleList,找不到返回false

 

注:找到第一个匹配的tuple就返回

 

lists:keyreplace(Key, N, TupleList1, NewTuple) -> TupleList2

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 11> lists:keyreplace(b,1,[{a,1,1},{b,1,1},{c,1,1}], {b,2,2}).  
  2. [{a,1,1},{b,2,2},{c,1,1}]  

替换list中第N位置为Key的tuple(只替换找到的第一个tuple)

 

lists:keystore(Key, N, TupleList1, NewTuple) -> TupleList2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 12> lists:keystore(b,1,[{a,1,1},{b,1,1},{c,1,1}],{b,2,2}).  
  2. [{a,1,1},{b,2,2},{c,1,1}]  

替换list中第N位置为Key的tuple,返回新的TupleList。没找不到将NewTuple附加到原有TupleList后面并返回,这是和lists:keyreplace/4的区别

 

注: 只替换找到的第一个tuple

 

lists函数小评

BIF函数

lists函数有一些已经被优化成bif函数,效率较高。有以下几个:

lists:member/2,  lists:reverse/2,  lists:keymember/3,  lists:keysearch/3,  lists:keyfind/3

 

性能不佳的函数

下面是一些实现性能不佳的函数,不建议比较长的list使用,短的list无所谓了:

1)lists:foldr/3 

非尾递归实现,如果顺序很重要的话,可以lists:reverse/1后lists:foldl/3,或者lists:foldl/3后lists:reverse/1

lists:foldr类似lists:foldl,不同的是foldr从列表最后一个元素开始,非尾递归实现,不建议长列表使用

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. foldr(F, Accu, [Hd|Tail]) ->  
  2.     F(Hd, foldr(F, Accu, Tail));  
  3. foldr(F, Accu, []) when is_function(F, 2) ->   
  4.    Accu.  

 

2)lists:append/2 

实现为append(L1, L2) -> L1 ++ L2. 其中,L1 ++ L2会遍历 L1,如果一定要使用就把短的list放左边

3)lists:subtract/2

实现为subtract(L1, L2) -> L1 -- L2. 其中,--的复杂度和它的操作数的长度的乘积成正比,替代方案如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Set = gb_sets:from_list(L2),   
  2.  [E || E <- L1, not gb_sets:is_element(E, Set)].  

注:如果L1中包含重复的元素,那么以上代码跟--的效果不同,--不会删掉所有重复的元素

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 13> [1,2,3,4,2] -- [2,3].  
  2. [1,4,2]  

4)lists:flatten/1

这个是list扁平化函数,这个存在性能开销,erlang的官方文档也有说明:
lists:flatten/1 builds an entirely new list. Therefore, it is expensive, and even more expensive than the ++ (which copies its left argument, but not its right argument).
它会拷贝DeepList中所有嵌套的元素,生成一个新的列表,代价比较高,长列表不建议使用。

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. flatten(List) when is_list(List) ->  
  2.     do_flatten(List, []).  
  3.   
  4. flatten(List, Tail) when is_list(List), is_list(Tail) ->  
  5.     do_flatten(List, Tail).  
  6.   
  7. do_flatten([H|T], Tail) when is_list(H) ->  
  8.     do_flatten(H, do_flatten(T, Tail));  
  9. do_flatten([H|T], Tail) ->  
  10.     [H|do_flatten(T, Tail)];  
  11. do_flatten([], Tail) ->  
  12.     Tail.  

5)lists中非尾递归实现的函数:

lists:map/2, lists:flatmap/2, lists:zip/2, lists:delete/2, lists:sublist/2, lists:sublist/3, lists:takewhile/2, lists:concat/1
lists:flatten/1, lists:keydelete/3,  lists:keystore/4, lists:zf/2, lists:mapfoldl/3, lists:mapfoldr/3, lists:foldr/3

 

带匿名函数的lists函数

lists有很多函数都带有匿名函数的参数项,有以下几个函数:

lists:foldl/3, lists:foldr/3, lists:foreach/2, lists:sort/2, lists:usort/2, lists:merge/3, lists:umerge/3, lists:keymap/3, lists:flatmap/2

匿名函数的使用有什么问题呢?

普通函数在erlang编译期就做了优化,匿名函数则要在代码执行期动态生成,造成一定的消耗。虽然现在匿名函数的过程已优化成本地函数了,但根据上下文取值,地址跳转还是不能避免。可能以后erlang会继续对匿名函数做改进

对lists匿名函数的处理的优化:

1)函数赋值

第一个问题就是不要在循环中写这样的代码:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 14> [lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].  
  2. [15,15,15,15,15]  

以上代码中,每次循环匿名函数都会重新生成,可以如下修改:

 

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 17> Fun = fun(X, Sum) -> X + Sum end.  
  2. #Fun<erl_eval.12.82930912>  
  3. 18> [lists:foldl(Fun, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].  
  4. [15,15,15,15,15]  

匿名函数Fun只要生成一次,作为参数放到lists函数即可

 

2)函数实体化

所谓实体化就是把匿名函数写成本地函数,然后再作为参数传给lists函数,如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. test() ->  
  2.     lists:foldl(fun sum/2, 0, [1,2,3,4,5]).  
  3.   
  4. sum(X, Sum) ->  
  5.     X + Sum.  

这里也有匿名函数动态生成的问题,有兴趣的可以打印汇编码看看具体情况

测试,这种方式效率稍微变低,原因是,每次调用时对比多一次函数调用[ i_call_only_f ] 

这里讨论另一种调用方式 fun M:F/A

 

[plain] view plain copy
 
  1. test() ->  
  2.     lists:foldl(fun ?MODULE:sum/2, 0, [1,2,3,4,5]).  
  3.   
  4. sum(X, Sum) ->  
  5.     X + Sum.  

如果是这种调用,执行过程中,会先调用bif函数 erlang:make_fun/3 找到这个导出函数的代码地址,然后生成匿名函数结构数据,再像函数外部调用一样执行代码。如果将匿名函数赋值,然后再执行,单算执行效率的话就要比前面两种高。不过整体计算是比较低效的,因为erlang:make_fun/3 是根据函数名,方法名,参数个数去查找导出函数表,导出函数表是哈希桶实现,还是有一点效率开销。

 

列表解析

说到lists,不得不说列表解析,在之前的文章也谈过列表解析。列表解析的基本形式如下:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. [Expr(E) || E <- List]  

简单的例子如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 19> [X+1||X<-[1,2,3,4,5]].  
  2. [2,3,4,5,6]  

在列表解析表达式中,|| 左边用以生成列表元素,相当于构造器;右边由赋值语句和条件语句构成,也可以只有赋值语句。实际上,列表解析在编译时会优化一个本地函数,没必要过于担心它的性能。

使用列表解析注意以下问题就好:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 20> [X+1||X<-[1,2,3,4,5]],ok.  
  2. ok  

在上面的例子中,erlang对列表解析做了优化,如果列表解析的结果明显不会被用到,列表根本不会被构建。在这里,列表解析后加个ok可以激活erlang的优化

 

 

list列表元素打乱

列表元素打乱,就是将列表中元素的顺序随机化,打乱原来的顺序。

方法很简单,如下:

 

[plain] view plain copy
 
  1. shuffle(L) ->  
  2.     List1 = [{random:uniform(), X} || X <- L],   
  3.     List2 = lists:keysort(1, List1),   
  4.     [E || {_, E} <- List2].   

 

注:random:uniform() 的随机化种子放在进程字典中。为了提高随机化,以上函数调用前或调用进程启动时执行一次 random:seed(erlang:now())

 

erlang有两种复合结构,tuple和list,两者的区别是tuple子元素的个数是固定不变的,声明后就不能改变了;而list是可变的,可以通过[H|T]来取出或插入新元素。上篇文章讲了tuple相关的内容,本篇就讲erlang list方面的知识,主要说一些基本操作和常用的list函数,再讲一些可以优化的点。

list基本操作

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 1> A = [1, 2, 3, "abc"].  
  2. [1,2,3,"abc"]  
  3. 2> length(A).  
  4. 4  
  5. 3> is_list(A).  
  6. true  
  7. 4> list_to_binary(A).  
  8. <<1,2,3,97,98,99>>  
  9. 5> list_to_bitstring(A).  
  10. <<1,2,3,97,98,99>>  
  11.   
  12.   
  13. 6> [A1|A2] = A.  
  14. [1,2,3,"abc"]  
  15. 7> A1.  
  16. 1  
  17. 8> A2.  
  18. [2,3,"abc"]  
  19. 9> hd(A).  
  20. 1  
  21. 10> tl(A).  
  22. [2,3,"abc"]  
  23. 11> B = [0|A].  
  24. [0,1,2,3,"abc"]  

 

list函数说明

常用的lists函数

这里介绍一些常用的lists函数,其他看erlang lists文档

lists:foreach(Fun, List) -> ok

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 1> lists:foreach(fun(X) -> io:format("~p~n", [X]) end, [1,2,3]).  
  2. 1  
  3. 2  
  4. 3  
  5. ok  

例子中,通过遍历列表[1,2,3],把取出的元素给X,直到遍历结束,最后返回ok

 

lists:foldl(Fun, Acc0, List) -> Acc1

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 2> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).  
  2. 15  

例子中,0指定了Sum的初值,通过遍历列表[1,2,3,4,5],把取出的元素给X,再计算X+Sum,结果带入下次遍历传给 Sum,直到最后一次遍历将X+Sum的结果作为返回值

下面是lists:foldl/3 的实现,实际上是一个尾递归函数

foldl(F, Accu, [Hd|Tail]) ->
    foldl(F, F(Hd, Accu), Tail);
foldl(F, Accu, []) when is_function(F, 2) ->

    Accu.

 

lists:reverse(List1) -> List2

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 3> lists:reverse([1,2,3,4,5]).  
  2. [5,4,3,2,1]  

list反转(这是个bif函数,效率较高)

 

lists:flatten(DeepList) -> List

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 4> lists:flatten([1,[2,3],[4,[5]],6]).  
  2. [1,2,3,4,5,6]  

 

lists扁平化,将有深度结构的list转成一个简单的list。这个函数有性能开销,见文章后面说明。

 

lists:member(Elem, List) -> boolean()

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 5> lists:member(3,[1,2,3]).  
  2. true  

检查元素是否存在list中(这是个bif函数,效率较高)

 

lists:sort(List1) -> List2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 6> lists:sort([2,3,1]).  
  2. [1,2,3]  

lists元素排序,排序从小到大,如果想自定义排序规则,可以使用lists:sort/2

 

lists:sort(Fun, List1) -> List2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 7> lists:sort(fun(A,B)-> A>=B end,[2,3,1]).  
  2. [3,2,1]  

 

 

处理TupleList的lists函数

TupleList是元素是tuple的list,erlang提供了一些接口专门处理这类数据。

lists:keymember(Key, N, TupleList) -> boolean()

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 8> lists:keymember(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. true  

检查list中是否有第N位置为Key的tuple(这是个bif函数,效率较高)

 


lists:keyfind(Key, N, TupleList) -> Tuple | false

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 9> lists:keyfind(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. {b,1,1}  

返回list中第N位置为Key的tuple,找不到返回false

 

注:

 1. 找到第一个匹配的tuple就返回,找不到返回false

 2. 这个接口可以替代lists:keysearch(Key, N, TupleList) 使用

 3. 这是个bif函数,效率较高

lists:keysearch(Key, N, TupleList)的官方文档中有这么一句:

This function is retained for backward compatibility. The function lists:keyfind/3 (introduced in R13A) is in most cases more convenient.

意思就是说lists:keysearch/3只是为了保持向后兼容,使用lists:keyfind/3会更方便


lists:keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2} | false

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 10> lists:keytake(b,1,[{a,1,1},{b,1,1},{c,1,1}]).  
  2. {value,{b,1,1},[{a,1,1},{c,1,1}]}  

返回list中第N位置为Key的tuple,并返回找到的tuple和剩下的TupleList,找不到返回false

 

注:找到第一个匹配的tuple就返回

 

lists:keyreplace(Key, N, TupleList1, NewTuple) -> TupleList2

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 11> lists:keyreplace(b,1,[{a,1,1},{b,1,1},{c,1,1}], {b,2,2}).  
  2. [{a,1,1},{b,2,2},{c,1,1}]  

替换list中第N位置为Key的tuple(只替换找到的第一个tuple)

 

lists:keystore(Key, N, TupleList1, NewTuple) -> TupleList2

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 12> lists:keystore(b,1,[{a,1,1},{b,1,1},{c,1,1}],{b,2,2}).  
  2. [{a,1,1},{b,2,2},{c,1,1}]  

替换list中第N位置为Key的tuple,返回新的TupleList。没找不到将NewTuple附加到原有TupleList后面并返回,这是和lists:keyreplace/4的区别

 

注: 只替换找到的第一个tuple

 

lists函数小评

BIF函数

lists函数有一些已经被优化成bif函数,效率较高。有以下几个:

lists:member/2,  lists:reverse/2,  lists:keymember/3,  lists:keysearch/3,  lists:keyfind/3

 

性能不佳的函数

下面是一些实现性能不佳的函数,不建议比较长的list使用,短的list无所谓了:

1)lists:foldr/3 

非尾递归实现,如果顺序很重要的话,可以lists:reverse/1后lists:foldl/3,或者lists:foldl/3后lists:reverse/1

lists:foldr类似lists:foldl,不同的是foldr从列表最后一个元素开始,非尾递归实现,不建议长列表使用

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. foldr(F, Accu, [Hd|Tail]) ->  
  2.     F(Hd, foldr(F, Accu, Tail));  
  3. foldr(F, Accu, []) when is_function(F, 2) ->   
  4.    Accu.  

 

2)lists:append/2 

实现为append(L1, L2) -> L1 ++ L2. 其中,L1 ++ L2会遍历 L1,如果一定要使用就把短的list放左边

3)lists:subtract/2

实现为subtract(L1, L2) -> L1 -- L2. 其中,--的复杂度和它的操作数的长度的乘积成正比,替代方案如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Set = gb_sets:from_list(L2),   
  2.  [E || E <- L1, not gb_sets:is_element(E, Set)].  

注:如果L1中包含重复的元素,那么以上代码跟--的效果不同,--不会删掉所有重复的元素

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 13> [1,2,3,4,2] -- [2,3].  
  2. [1,4,2]  

4)lists:flatten/1

这个是list扁平化函数,这个存在性能开销,erlang的官方文档也有说明:
lists:flatten/1 builds an entirely new list. Therefore, it is expensive, and even more expensive than the ++ (which copies its left argument, but not its right argument).
它会拷贝DeepList中所有嵌套的元素,生成一个新的列表,代价比较高,长列表不建议使用。

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. flatten(List) when is_list(List) ->  
  2.     do_flatten(List, []).  
  3.   
  4. flatten(List, Tail) when is_list(List), is_list(Tail) ->  
  5.     do_flatten(List, Tail).  
  6.   
  7. do_flatten([H|T], Tail) when is_list(H) ->  
  8.     do_flatten(H, do_flatten(T, Tail));  
  9. do_flatten([H|T], Tail) ->  
  10.     [H|do_flatten(T, Tail)];  
  11. do_flatten([], Tail) ->  
  12.     Tail.  

5)lists中非尾递归实现的函数:

lists:map/2, lists:flatmap/2, lists:zip/2, lists:delete/2, lists:sublist/2, lists:sublist/3, lists:takewhile/2, lists:concat/1
lists:flatten/1, lists:keydelete/3,  lists:keystore/4, lists:zf/2, lists:mapfoldl/3, lists:mapfoldr/3, lists:foldr/3

 

带匿名函数的lists函数

lists有很多函数都带有匿名函数的参数项,有以下几个函数:

lists:foldl/3, lists:foldr/3, lists:foreach/2, lists:sort/2, lists:usort/2, lists:merge/3, lists:umerge/3, lists:keymap/3, lists:flatmap/2

匿名函数的使用有什么问题呢?

普通函数在erlang编译期就做了优化,匿名函数则要在代码执行期动态生成,造成一定的消耗。虽然现在匿名函数的过程已优化成本地函数了,但根据上下文取值,地址跳转还是不能避免。可能以后erlang会继续对匿名函数做改进

对lists匿名函数的处理的优化:

1)函数赋值

第一个问题就是不要在循环中写这样的代码:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 14> [lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].  
  2. [15,15,15,15,15]  

以上代码中,每次循环匿名函数都会重新生成,可以如下修改:

 

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 17> Fun = fun(X, Sum) -> X + Sum end.  
  2. #Fun<erl_eval.12.82930912>  
  3. 18> [lists:foldl(Fun, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].  
  4. [15,15,15,15,15]  

匿名函数Fun只要生成一次,作为参数放到lists函数即可

 

2)函数实体化

所谓实体化就是把匿名函数写成本地函数,然后再作为参数传给lists函数,如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. test() ->  
  2.     lists:foldl(fun sum/2, 0, [1,2,3,4,5]).  
  3.   
  4. sum(X, Sum) ->  
  5.     X + Sum.  

这里也有匿名函数动态生成的问题,有兴趣的可以打印汇编码看看具体情况

测试,这种方式效率稍微变低,原因是,每次调用时对比多一次函数调用[ i_call_only_f ] 

这里讨论另一种调用方式 fun M:F/A

 

[plain] view plain copy
 
  1. test() ->  
  2.     lists:foldl(fun ?MODULE:sum/2, 0, [1,2,3,4,5]).  
  3.   
  4. sum(X, Sum) ->  
  5.     X + Sum.  

如果是这种调用,执行过程中,会先调用bif函数 erlang:make_fun/3 找到这个导出函数的代码地址,然后生成匿名函数结构数据,再像函数外部调用一样执行代码。如果将匿名函数赋值,然后再执行,单算执行效率的话就要比前面两种高。不过整体计算是比较低效的,因为erlang:make_fun/3 是根据函数名,方法名,参数个数去查找导出函数表,导出函数表是哈希桶实现,还是有一点效率开销。

 

列表解析

说到lists,不得不说列表解析,在之前的文章也谈过列表解析。列表解析的基本形式如下:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. [Expr(E) || E <- List]  

简单的例子如下:

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 19> [X+1||X<-[1,2,3,4,5]].  
  2. [2,3,4,5,6]  

在列表解析表达式中,|| 左边用以生成列表元素,相当于构造器;右边由赋值语句和条件语句构成,也可以只有赋值语句。实际上,列表解析在编译时会优化一个本地函数,没必要过于担心它的性能。

使用列表解析注意以下问题就好:

 

[plain] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. 20> [X+1||X<-[1,2,3,4,5]],ok.  
  2. ok  

在上面的例子中,erlang对列表解析做了优化,如果列表解析的结果明显不会被用到,列表根本不会被构建。在这里,列表解析后加个ok可以激活erlang的优化

 

 

list列表元素打乱

列表元素打乱,就是将列表中元素的顺序随机化,打乱原来的顺序。

方法很简单,如下:

 

[plain] view plain copy
 
  1. shuffle(L) ->  
  2.     List1 = [{random:uniform(), X} || X <- L],   
  3.     List2 = lists:keysort(1, List1),   
  4.     [E || {_, E} <- List2].   

 

注:random:uniform() 的随机化种子放在进程字典中。为了提高随机化,以上函数调用前或调用进程启动时执行一次 random:seed(erlang:now())

 

posted @ 2017-04-01 11:59  Shay_黄  阅读(2699)  评论(0编辑  收藏  举报