我从最基本的角度学习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:我知道这个特殊的问题是通过列表推导解决的,但我想用递归来做。
答案 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)时间。