【问题标题】:Erlang: Can this be done without lists:reverse?Erlang:这可以在没有列表的情况下完成吗:反向?
【发布时间】:2011-05-31 03:30:21
【问题描述】:

我是学习 Erlang 的初学者。在阅读了 Erlang 中的列表解析和递归之后,我想尝试实现自己的 map 函数,结果如下:

% Map: Map all elements in a list by a function
map(List,Fun) -> map(List,Fun,[]).
map([],_,Acc) -> lists:reverse(Acc);
map([H|T],Fun,Acc) -> map(T,Fun,[Fun(H)|Acc]).

我的问题是:通过递归函数建立一个列表,然后在最后反转它,感觉不对。有没有办法以正确的顺序建立列表,所以我们不需要反过来?

【问题讨论】:

    标签: erlang tail-recursion


    【解决方案1】:

    为了理解为什么累加和反转非常快,您必须了解列表是如何在 Erlang 中构建的。像 Lisp 中的那些 Erlangs 列表是由 cons cells 构建的(查看链接中的图片)。

    在像 Erlang 列表这样的单链表中,预先添加一个元素(或 列表)非常便宜。这就是 List = [H|T] 构造所做的。

    反转由 cons 单元组成的单链表非常快,因为您只需要遍历列表,只需将下一个元素添加到您已经反转的部分结果。正如已经提到的,它也是在 Erlang 中用 C 语言实现的。

    通常也可以通过尾递归函数来构建反向顺序的结果,这意味着不会构建堆栈并且(仅在旧版本的 Erlang 中!)因此可以节省一些内存.

    说了这么多:这是The Eight Myths of Erlang Performance 之一,在尾递归函数中反向构建并在最后调用lists:reverse/1 总是更好。

    这是一个没有lists:reverse/1 的正文递归版本,这将在 12 之前的 Erlang 版本上使用更多内存,但在当前版本上不会如此:

    map([H|T], Fun) ->
        [ Fun(H) | map(T,Fun) ];
    map([],_) -> [].
    

    这是一个使用列表推导的地图版本:

    map(List, Fun) ->
        [ Fun(X) || X <- List ].
    

    如您所见,这非常简单,因为map 只是列表推导的内置部分,因此您可以直接使用列表推导,而不再需要map

    作为一个额外的纯 Erlang 实现,它显示了如何有效地反转 cons 单元列表(在 Erlang 中,调用 lists:reverse/1 总是更快,因为它是在 C 中,但做同样的事情)。

    reverse(List) ->
        reverse(List, []).
    
    reverse([H|T], Acc) ->
        reverse(T, [H|Acc]);
    reverse([], Acc) ->
        Acc.
    

    如您所见,列表中只有 [A|B] 操作,将 cons 单元分开(在模式匹配时)并在执行 [H|Acc] 时构建新单元。

    【讨论】:

      【解决方案2】:
      1. 这样的结构[元素| Acc] 然后列出:reverse(Acc) 是函数式编程中相当常见的模式。
      2. 您可以使用以正确顺序存储数据的累加器编写代码,但此代码将比您问题中的代码慢(提示:使用 Acc ++ [Fun(H)])。
      3. 实际上在 Erlang 列表中:reverse 是用 C 实现的,而且运行速度非常快。

      【讨论】:

      • 第 2 点。代码会很多慢,实际上它会有 O(n^2) 的行为,这是要避免的。如果您不想反转,只需使用主体递归而不是尾递归并省略 Acc
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-10
      • 2014-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多