Erlang:这可以在没有列表的情况下完成:反向吗?

时间:2011-05-30 18:09:46

标签: erlang tail-recursion

我是初学者学习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]).

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

2 个答案:

答案 0 :(得分:21)

要了解为什么累积和反转速度非常快,您必须了解如何在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]时构建新的操作。

答案 1 :(得分:-1)

  1. 这种结构[Element | Acc]然后列出:反向(Acc)是函数式编程中非常常见的模式。
  2. 您可以使用以正确顺序存储数据的累加器编写代码,但此代码将慢于您的问题中的代码(提示:使用Acc ++ [Fun(H)])。
  3. 实际上在Erlang列表中:反向是在C中实现的,它的工作速度非常快。