我正在阅读http://learnyousomeerlang.com/,其中包含一个尾递归子列表函数,该函数反转列表以保持顺序。我写了一个不需要反向调用的替代方案。我的效率更高(当然更冗长,但我并不关心),还是我忽视了什么?
-module(sublist).
-export([sublist/2,sublistR/2]).
-include_lib("eunit/include/eunit.hrl").
sublist(_,0) ->
[];
sublist([],_) ->
[];
sublist(List,Length) ->
sublist([hd(List)], tl(List), Length-1).
sublist(Acc,[],_) ->
Acc;
sublist(Acc,_,0) ->
Acc;
sublist(Acc,Tail,Length) ->
sublist(Acc++[hd(Tail)], tl(Tail), Length-1).
sublistR(L, N) -> lists:reverse(sublistR(L, N, [])).
sublistR(_, 0, SubList) -> SubList;
sublistR([], _, SubList) -> SubList;
sublistR([H|T], N, SubList) when N > 0 ->
sublistR(T, N-1, [H|SubList]).
sublist_test() ->
sublisttest(fun sublist:sublist/2),
sublisttest(fun sublist:sublistR/2).
sublisttest(SublistFunc) ->
[] = SublistFunc([],10),
[] = SublistFunc([1,2,3], 0),
[1,2,3] = SublistFunc([1,2,3],3),
[1,2,3] = SublistFunc([1,2,3],4),
[1,2] = SublistFunc([1,2,3],2).
答案 0 :(得分:4)
没有。但是不要感觉不好,这是每个人都必须尽早达成协议的事情。
关于这句话的全部内容:
Acc ++ [hd(Tail)]
我们说Acc = [1,2,3]
是真的。然后Acc ++ [hd(Tail)]
直接等同于[1,2,3 | [Head]]
。这是什么意思?
在这种特殊情况下,它意味着完全与在Acc
中作为汽车写出每个元素并使用{{1}的结果相同作为一个新的缺点的cdr。这意味着将单个元素连接到现有列表的末尾,必须遍历整个列表(用于解构,以显示最终的cdr)。另一方面,将单个元素添加到列表的开头是一个简单的操作。
使用[hd(Tail)]
的神奇之处(以及使用cons风格列表的语言中的惯用语的全部原因)是它是用C语言实现的数组操作,而不是对象语言的扩展。然后重新写作。
这个访问最后一个元素的问题是昂贵的VS访问第一个元素是lists:reverse/1
(或其他语言中的hd/1
)返回列表的第一个元素的原因,但是{{1 (或其他语言中的head()
)返回除列表的第一个元素之外的所有内容。保证访问列表最后一个元素的函数的使用通常被“这很慢!”的警告所包围。
这也是为什么很少看到有人在几乎所有语言中使用tl/1
的原因。如果确实需要从右侧折叠(例如,由于非交换操作),他们通常会先tail()
,然后调用foldr
。 lists:reverse/1
可以是尾递归,但真正的foldl
不能。
实际上,Erlang文档中提到了这一点:http://www.erlang.org/doc/man/lists.html#foldl-3
从右侧折叠的成本随着列表长度的增加而增加。这就是为什么会发生这种情况:
foldl
13μs与31291μs。哎哟。
另见: