在erlang中进行尾递归
我从最基本的角度学习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:我知道这个问题是用列表解析来解决的,但我想用递归来实现。
对于这种操作,你应该使用列表理解:
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]
,尾部是列表。 你可以使用Result ++ [Head+1]
但是这会在每次递归调用时执行Result列表的副本。
您还可以查看lists:map/2
的代码lists:map/2
,它不是尾递归,但似乎在这种情况下编译器的实际优化工作得很好:
inc([H|T]) -> [H+1|inc(T)];
inc([]) -> [].
[编辑]
列表的内部和隐藏表示看起来像链式列表。 每个元素都包含一个术语和对尾部的引用。 因此,在头部添加一个元素不需要修改现有列表,但在最后添加一些内容需要修改最后一个元素(对空列表的引用被对新子列表的引用所替代)。 由于变量不可变,所以需要修改最后一个元素的修改副本,然后需要修改列表中的前一个元素等等。 据我所知,编译器的优化不会决定变异变量(从文档中扣除)。
以相反顺序生成结果的函数是将新增加的元素添加到Result
列表前面的自然Result
。 这并不罕见,推荐的“修复”是简单地list:reverse/1
返回前的输出。
在这种情况下,您可以简单地使用++
运算符而不是[H|T]
“cons”运算符以反方式加入结果,并以正确的顺序给出所需的输出:
sum_list_2([Head|Tail], Result)->
sum_list_2(Tail, Result ++ [Head + 1]);
不建议这么做,因为++
操作符总是复制左手操作数(越来越大),导致算法在O(n ^ 2)时间内运行,而不是[Head + 1 | Tail]
[Head + 1 | Tail]
版本的O(n)时间。