在erlang中的尾递归

时间:2015-02-18 15:48:34

标签: recursion erlang tail-recursion tail

我从最基本的角度学习Erlang并且有尾递归函数的问题。我希望我的函数接收一个列表并返回一个新的列表,其中element = element + 1.例如,如果我发送[1,2,3,4,5]作为参数,它必须返回[2,3,4] ,5,6]。问题是,当我发送精确的参数时,它会返回[[[[[[] | 2] | 3] | 4] | 5] | 6]。

我的代码是:

-module(test).
-export([test/0]).

test()->
  List = [1,2,3,4,5],
  sum_list_2(List).

sum_list_2(List)->
  sum_list_2(List,[]).

sum_list_2([Head|Tail], Result)->
  sum_list_2(Tail,[Result|Head +1]);
sum_list_2([], Result)->
  Result.

但是,如果我将我的功能更改为:

sum_list_2([Head|Tail], Result)->
  sum_list_2(Tail,[Head +1|Result]);
sum_list_2([], Result)->
  Result.

输出[6,5,4,3,2]即可。为什么函数没有反过来工作([Result | Head + 1]输出[2,3,4,5,6])?

PS:我知道这个特殊的问题是通过列表推导解决的,但我想用递归来做。

2 个答案:

答案 0 :(得分:2)

对于这种操作,你应该使用列表理解:

1> L = [1,2,3,4,5,6].
[1,2,3,4,5,6]
2> [X+1 || X <- L].
[2,3,4,5,6,7]

这是最快,最惯用的方式。

对你的第一个版本的评论:[Result|Head +1]构建一个不正确的列表。构造总是[Head|Tail],其中Tail是一个列表。您可以使用Result ++ [Head+1],但这会在每次递归调用时执行结果列表的副本。

您还可以查看lists:map/2的代码,它不是尾递归的,但在这种情况下,编译器的实际优化似乎运行良好:

inc([H|T]) -> [H+1|inc(T)];
inc([]) -> [].

<强> [编辑]

列表的内部和隐藏表示看起来像链式列表。每个元素都包含一个术语和对尾部的引用。因此,在头顶部添加元素不需要修改现有列表,但最后添加一些内容需要改变最后一个元素(对空列表的引用被对新子列表的引用替换)。由于变量不可变,因此需要制作最后一个元素的修改副本,而后者又需要改变列表的前一个元素,依此类推。据我所知,编译器的优化并没有决定改变变量(从documentation中扣除)。

答案 1 :(得分:-1)

以相反顺序生成结果的函数是将新增加的元素添加到Result列表的的自然结果。这并不常见,推荐的&#34;修复&#34;在返回之前简单地list:reverse/1输出。

虽然在这种情况下,您只需使用++运算符代替[H|T]&#34; cons&#34;操作员以相反的方式加入您的结果,以正确的顺序为您提供所需的输出:

sum_list_2([Head|Tail], Result)->
  sum_list_2(Tail, Result ++ [Head + 1]);
不建议这样做,因为++运算符always copies它(越来越大)左手操作数,导致算法在O(n ^ 2)时间内运行而不是[Head + 1 | Tail]版本的O(n)时间。