【问题标题】:Erlang sublist function performanceErlang 子列表函数性能
【发布时间】:2015-02-12 02:29:29
【问题描述】:

我正在阅读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).

【问题讨论】:

  • tl;dr: 不。但不要灰心——你问的正是正确的问题类型,并在代码中进行了正确的实验,以真正从你的自我中获得一些东西——学习。这是一件很棒的事情!

标签: erlang


【解决方案1】:

没有。但不要难过,这是每个人都必须尽早接受的普遍现象。

这就是这句话的全部内容:

Acc ++ [hd(Tail)]

假设Acc = [1,2,3] 是真的。那么Acc ++ [hd(Tail)]直接等价于[1,2,3 | [Head]]。这是什么意思?

在这种特殊情况下,这意味着它完全与写出Acc中的每个元素作为汽车并使用[hd(Tail)]的结果作为汽车cdr 在一个新的缺点。这意味着要将单个元素连接到现有列表的末尾,必须遍历整个列表(为了解构,以暴露最终的 cdr)。另一方面,将单个元素添加到列表的开头 是一个简单的操作。

使用lists:reverse/1 的魔力(以及这在使用 cons 样式列表的语言中是惯用的全部原因)在于它是用 C 实现的数组操作,而不是您正在编写的对象语言的扩展就在那时。

访问最后一个元素与访问第一个元素相比代价高昂的问题是hd/1(或其他语言中的head())返回列表的第一个元素,但tl/1(或@987654337)的原因@ 在其他语言中)返回列表的第一个元素以外的所有内容。保证访问列表的最后一个元素的函数的使用通常被“这很慢!”的警告包围。

这也是您很少看到有人在几乎任何语言中使用foldr 的原因。如果他们真的需要从右边折叠(例如,由于非交换操作),他们通常会先执行lists:reverse/1,然后调用foldl。 foldl 可以是尾递归的,但真正的 foldr 不能。

实际上,Erlang 文档中提到了这一点:http://www.erlang.org/doc/man/lists.html#foldl-3

从右边折叠的成本随着列表长度的增加而增加。这就是发生这种情况的原因:

1> AMillionThings = lists:seq(1, 1000000).
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
 23,24,25,26,27,28,29|...]
2> OneThing = [0].
[0]
3> timer:tc(fun() -> OneThing ++ AMillionThings end).
{13,
 [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
  23,24,25,26|...]}
4> timer:tc(fun() -> AMillionThings ++ OneThing end).
{31291,
 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
  23,24,25,26,27|...]}

13μs 与 31291μs。哎哟。

另见:

【讨论】:

  • 附言。 timer:tc 模块以微秒 (us) 而不是毫秒 (ms) 为单位返回经过的时间,erlang.org/doc/man/timer.html#tc-1
  • @Shawn 确实,我使用了错误的时间单位符号!我现在已经更正了。感谢您的提醒。 :-)
猜你喜欢
  • 2010-11-30
  • 2015-03-23
  • 2011-12-17
  • 2016-08-25
  • 1970-01-01
  • 2015-02-01
  • 1970-01-01
  • 2021-12-08
  • 2021-01-19
相关资源
最近更新 更多