添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

list基本操作

[plain] view plain copy

例子中,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

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

替换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
  • foldr(F, Accu, [Hd|Tail]) ->
  • F(Hd, foldr(F, Accu, Tail));
  • foldr(F, Accu, []) when is_function(F, 2) ->
  • 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
  • do_flatten([H|T], Tail) when is_list(H) ->
  • do_flatten(H, do_flatten(T, Tail));
  • do_flatten([H|T], Tail) ->
  • [H|do_flatten(T, Tail)];
  • do_flatten([], Tail) ->
  • 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
  • 14> [lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].
  • [15,15,15,15,15]
  • 以上代码中,每次循环匿名函数都会重新生成,可以如下修改:

  • 17> Fun = fun(X, Sum) -> X + Sum end.
  • #Fun<erl_eval.12.82930912>
  • 18> [lists:foldl(Fun, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].
  • [15,15,15,15,15]
  • 匿名函数Fun只要生成一次,作为参数放到lists函数即可

    2)函数实体化

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

    [plain] view plain copy

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

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

    [plain] view plain copy

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

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

    [plain] view plain copy

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

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

    list基本操作

    [plain] view plain copy

    例子中,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

    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

    替换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
  • foldr(F, Accu, [Hd|Tail]) ->
  • F(Hd, foldr(F, Accu, Tail));
  • foldr(F, Accu, []) when is_function(F, 2) ->
  • 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
  • do_flatten([H|T], Tail) when is_list(H) ->
  • do_flatten(H, do_flatten(T, Tail));
  • do_flatten([H|T], Tail) ->
  • [H|do_flatten(T, Tail)];
  • do_flatten([], Tail) ->
  • 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
  • 14> [lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].
  • [15,15,15,15,15]
  • 以上代码中,每次循环匿名函数都会重新生成,可以如下修改:

  • 17> Fun = fun(X, Sum) -> X + Sum end.
  • #Fun<erl_eval.12.82930912>
  • 18> [lists:foldl(Fun, 0, [1,2,3,4,5]) || X <- lists:seq(1,5)].
  • [15,15,15,15,15]
  • 匿名函数Fun只要生成一次,作为参数放到lists函数即可

    2)函数实体化

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

    [plain] view plain copy

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

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

    [plain] view plain copy

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

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