右旋转Erlang中的列表

右旋转Erlang中的列表,erlang,functional-programming,Erlang,Functional Programming,我现在已经熟悉了顺序Erlang(以及函数式编程思想)。因此,我想在没有BIF帮助的情况下实现以下两个功能。一个是left\u rotate(我已经提出了解决方案),另一个是right\u rotate(我在这里询问) 我不想在这个练习中使用BIF。如何实现正确的旋转 一个相关的问题和稍微重要一点的问题。我如何知道我的一个实现是有效的还是无效的(例如,如果我在BIF的帮助下实现了相同的东西,那么可以避免不必要的递归,等等) 我认为构建BIF是为了提供一些函数来提高函数编程不擅长的效率(或者如果我

我现在已经熟悉了顺序Erlang(以及函数式编程思想)。因此,我想在没有BIF帮助的情况下实现以下两个功能。一个是
left\u rotate
(我已经提出了解决方案),另一个是
right\u rotate
(我在这里询问)

我不想在这个练习中使用BIF。如何实现正确的旋转

一个相关的问题和稍微重要一点的问题。我如何知道我的一个实现是有效的还是无效的(例如,如果我在BIF的帮助下实现了相同的东西,那么可以避免不必要的递归,等等)


我认为构建BIF是为了提供一些函数来提高函数编程不擅长的效率(或者如果我们以“函数方式”来实现它们,那么性能就不是最优的)
rightrotate( List, 0 ) ->
  List;
rightrotate( List, Times ) ->
  lists:reverse( leftrotate( lists:reverse( List ), Times ) ).

不要说这是最好的主意或任何东西:)

首先,您的实现有点缺陷(请在空列表中尝试……)

第二,我向你建议如下:

-module(foo). -export([left/2, right/2]). left(List, Times) -> left(List, Times, []). left([], Times, Acc) when Times > 0 -> left(reverse(Acc), Times, []); left(List, 0, Acc) -> List ++ reverse(Acc); left([H|T], Times, Acc) -> left(T, Times-1, [H|Acc]). right(List, Times) -> reverse(foo:left(reverse(List), Times)). reverse(List) -> reverse(List, []). reverse([], Acc) -> Acc; reverse([H|T], Acc) -> reverse(T, [H|Acc]). test(Params) -> {Time1, _} = timer:tc(?MODULE, function1, Params), {Time2, _} = timer:tc(?MODULE, function2, Params), {{solution1, Time1}, {solution2, Time2}}. -模块(foo)。 -导出([左/2,右/2])。 左(列表,次数)-> 左(列表,时间,[])。 左([],次,Acc)时间>0-> 左(倒车(Acc),时间,[]); 左(列表,0,Acc)-> 列表++反向(Acc); 左([H | T],次,Acc)-> 左(T,乘以-1,[H|Acc])。 右(列表、次数)-> 反转(foo:left(反转(列表),时间))。 反向(列表)-> 相反(列表,[])。 反向([],Acc)-> 行政协调会; 反向([H|T],Acc)-> 反向(T[H|Acc])。 第三,为了对功能进行基准测试,您可以执行以下操作:

-module(foo). -export([left/2, right/2]). left(List, Times) -> left(List, Times, []). left([], Times, Acc) when Times > 0 -> left(reverse(Acc), Times, []); left(List, 0, Acc) -> List ++ reverse(Acc); left([H|T], Times, Acc) -> left(T, Times-1, [H|Acc]). right(List, Times) -> reverse(foo:left(reverse(List), Times)). reverse(List) -> reverse(List, []). reverse([], Acc) -> Acc; reverse([H|T], Acc) -> reverse(T, [H|Acc]). test(Params) -> {Time1, _} = timer:tc(?MODULE, function1, Params), {Time2, _} = timer:tc(?MODULE, function2, Params), {{solution1, Time1}, {solution2, Time2}}. 测试(参数)-> {Time1,{}=timer:tc(?模块,函数1,参数), {Time2,}=timer:tc(?模块,函数2,参数), {{solution1,Time1},{solution2,Time2}。 我没有对代码进行测试,所以请仔细查看它,了解它的意思。 此外,您可能希望实现自己的“反向”功能。通过使用尾部递归,它将变得微不足道。为什么不试试?

左:

lrl([], _N) ->
  [];

lrl(List, N) ->
  lrl2(List, List, [], 0, N).

% no more rotation needed, return head + rotated list reversed
lrl2(_List, Head, Tail, _Len, 0) ->
  Head ++ lists:reverse(Tail);

% list is apparenly shorter than N, start again with N rem Len
lrl2(List, [], _Tail, Len, N) ->
  lrl2(List, List, [], 0, N rem Len);

% rotate one
lrl2(List, [H|Head], Tail, Len, N) ->
  lrl2(List, Head, [H|Tail], Len+1, N-1).
对:

lrr([], _N) ->
  [];

lrr(List, N) ->
  L = erlang:length(List),
  R = N rem L,                        % check if rotation is more than length
  {H, T} = lists:split(L - R, List),  % cut off the tail of the list
  T ++ H.                             % swap tail and head

如果您需要更改项目顺序(如在轮换中),则列表不是正确的表示形式,因此您的实现将没有效率。(想象一个具有数千个作业的循环调度程序,在完成前一个作业并将其放在最后。)

因此,我们实际上只是在问自己,在列表中,用最少的开销做这件事的方法是什么。但是,我们想要摆脱的开销是什么呢?通过考虑(分配)更多的对象,或者通过其他方式,通常可以节省一点计算量。在计算过程中,通常可以有一个大于所需的活动集,并以这种方式保存分配


忽略带有空列表的角落案例等;上述代码将直接影响最终结果列表。分配的垃圾很少。最终列表是在堆栈展开时生成的。其代价是,在这个操作期间,我们需要为整个输入列表和正在构建的列表提供更多内存,但这是一个短暂的过渡过程。Java和Lisp对我造成的伤害使我开始考虑优化,但在Erlang中,你不必冒着全局完整GC的风险,它会扼杀所有实时属性的梦想。无论如何,我大体上喜欢上面的方法



这种方法使用一个名为Rev的临时列表,在我们将其传递给lists:reverse/1(它调用BIF-lists:reverse/2,但它没有做任何有趣的事情)之后,该列表就被处理掉了。通过创建这个临时的反向列表,我们避免了遍历列表两次。一次用于构建包含除最后一项以外的所有内容的列表,另一次用于获取最后一项。

您提到的效率问题与过度递归无关(函数调用很便宜),而与遍历和重建列表有关。每次向列表末尾添加内容时,都必须遍历并复制整个列表,这从append的实现中可以明显看出。因此,要旋转列表N个步骤,需要将整个列表复制N次。我们可以使用lists:split(如其他答案之一所示)一步完成整个旋转,但是如果我们事先不知道需要旋转多少步呢

列表确实不是此任务的理想数据结构。假设我们使用一对列表,一个用于头部,一个用于尾部,然后我们可以通过将元素从一个列表移动到另一个列表来轻松地旋转

因此,为了避免调用标准库中的任何内容,我们:

rotate_right(List, N) ->
    to_list(n_times(N, fun rotate_right/1, from_list(List))).

rotate_left(List, N) ->
    to_list(n_times(N, fun rotate_left/1, from_list(List))).

from_list(Lst) ->
    {Lst, []}.

to_list({Left, Right}) ->
    Left ++ reverse(Right).

n_times(0, _, X) -> X;
n_times(N, F, X) -> n_times(N - 1, F, F(X)).

rotate_right({[], []}) ->
    {[], []};
rotate_right({[H|T], Right}) ->
    {T, [H|Right]};
rotate_right({[], Right}) ->
    rotate_right({reverse(Right), []}).

rotate_left({[], []}) ->
    {[], []};
rotate_left({Left, [H|T]}) ->
    {[H|Left], T};
rotate_left({Left, []}) ->
    rotate_left({[], reverse(Left)}).

reverse(Lst) ->
    reverse(Lst, []).
reverse([], Acc) ->
    Acc;
reverse([H|T], Acc) ->
    reverse(T, [H|Acc]).

模块队列提供了类似这样的数据结构。不过,我写这篇文章时没有提到这一点,所以他们的可能更聪明。

对您的代码做一个简短的评论。我将更改您调用的append函数的名称。在函数上下文中,append通常意味着在列表的末尾添加一个新列表,而不仅仅是一个元素。增加混乱是没有意义的

如前所述:split不是BIF,它是一个用erlang编写的库函数。BIF的真正含义没有正确定义


拆分或类似拆分的解决方案看起来相当不错。正如有人已经指出的,列表并不是这种类型操作的最佳数据结构。当然,这取决于您使用它的目的。

值得注意的是,leftrotate和Roberto的left之间的区别在于,在leftrotate中,您为每个步骤调用append(相对于Tail以O(n)运行),给出了O(m*n)的总运行时间,其中m是要旋转的步骤数。使用累加器变量并在结束时将其反转是处理该问题的常用方法;罗伯托的左路是O(m+n)。是的,BIF通常效率更高,但在大多数情况下,正确使用算法更为重要。您也可以传递原始列表(参见我的答案)。它只是停留在堆栈上,因此没有开销。。。因此,您可以重新使用原始列表而不是r
rotate_right(List, N) ->
    to_list(n_times(N, fun rotate_right/1, from_list(List))).

rotate_left(List, N) ->
    to_list(n_times(N, fun rotate_left/1, from_list(List))).

from_list(Lst) ->
    {Lst, []}.

to_list({Left, Right}) ->
    Left ++ reverse(Right).

n_times(0, _, X) -> X;
n_times(N, F, X) -> n_times(N - 1, F, F(X)).

rotate_right({[], []}) ->
    {[], []};
rotate_right({[H|T], Right}) ->
    {T, [H|Right]};
rotate_right({[], Right}) ->
    rotate_right({reverse(Right), []}).

rotate_left({[], []}) ->
    {[], []};
rotate_left({Left, [H|T]}) ->
    {[H|Left], T};
rotate_left({Left, []}) ->
    rotate_left({[], reverse(Left)}).

reverse(Lst) ->
    reverse(Lst, []).
reverse([], Acc) ->
    Acc;
reverse([H|T], Acc) ->
    reverse(T, [H|Acc]).