【问题标题】:right rotate a List in Erlang右旋转Erlang中的列表
【发布时间】:2009-10-11 19:24:16
【问题描述】:

我现在开始熟悉 Sequential Erlang(以及函数式编程思想)。所以我想在没有 BIF 帮助的情况下实现以下两个功能。一个是left_rotate(我已经提出了解决方案),另一个是right_rotate(我在这里问)

-export(leftrotate/1, rightrotate/1).
%%(1) left rotate a lits
leftrotate(List, 0) ->
  List;
leftrotate([Head | Tail], Times) ->
  List = append(Tail, Head),
  leftrotate(List, Times -1).

append([], Elem)->
  [Elem];
append([H|T], Elem) ->
  [H | append(T, Elem)].

%%right rotate a list, how?
%%

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

一个相关的问题和稍微重要的问题。我怎么知道我的一个实现是否有效(即,如果我在 BIF 的帮助下实现相同的东西,避免不必要的递归等)

我认为 BIF 旨在提供一些函数来提高函数式编程不擅长的效率(或者如果我们以“函数式方式”执行它们,则性能不是最优的)。

【问题讨论】:

    标签: erlang functional-programming


    【解决方案1】:

    您提到的效率问题与过度递归无关(函数调用很便宜),而与遍历和重建列表有关。每次您在列表末尾添加一些内容时,您都必须遍历并复制整个列表,这从您的 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]).
    

    模块队列提供了类似这样的数据结构。不过我写的这个没有参考那个,所以他们的可能更聪明。

    【讨论】:

    • 在我看来,您刚刚“重新实现”了lists:split,并在末尾添加了一个附加信息。
    • 是的,如果您只使用所写的代码。我的目的是展示一种不同的数据存储方式,使相关操作(旋转一步)更容易。
    【解决方案2】:

    首先,您的实现有点错误(尝试使用空列表...)

    其次,我建议你这样做:

    -模块(foo)。 -导出([左/2,右/2])。 左(列表,时间)-> 左(列表,时间,[])。 left([], Times, Acc) 当 Times > 0 -> 左(反向(Acc),时间,[]); 左(列表,0,Acc)-> 列表++反向(Acc); 左([H|T],时间,Acc)-> 左(T,Times-1,[H|Acc])。 对(列表,时间)-> 反向(富:左(反向(列表),时代))。 反向(列表)-> 反向(列表,[])。 反向([],累加)-> 累加; 反向([H|T],Acc)-> 反向(T,[H|Acc])。

    第三,为了对函数进行基准测试,您可以执行以下操作:

    测试(参数)-> {Time1, _} = timer:tc(?MODULE, function1, Params), {Time2, _} = timer:tc(?MODULE, function2, Params), {{solution1, Time1}, {solution2, Time2}}。

    我没有测试代码,所以批判性地看它,只是想明白。 此外,您可能希望实现自己的“反向”功能。使用尾递归将是微不足道的。为什么不试试呢?

    【讨论】:

    • 值得注意的是,您的 leftrotate 和 Roberto 的 left 之间的区别在于,在 leftrotate 中,您为每一步调用 append(相对于 Tail 以 O(n) 运行),总运行时间为O(m*n),其中 m 是要旋转的步数。使用累加器变量并在最后反转它是处理该问题的常用方法;罗伯托的左路运行时间为 O(m+n)。 BIF 通常更有效,是的,但在大多数情况下,正确使用算法更为重要。
    • 您也可以传递原始列表(请参阅我的回答)。它只是留在堆栈上,所以没有开销......所以你可以重复使用原始列表而不是重新反转 Acc。
    • 您好 Roberto 和 legoscia,我了解 Roberto 的回答更有效,但它使用 BIF、lists:reverse。我想知道一种纯粹的非 BIF 方式:-)(仅用于本练习)
    • 注意,这个实现也不处理空列表。如果您调用 left([],3),它的 Acc 为空 [],因此在 left([],Times,[]) 处陷入无限循环。要突破,你应该处理 left([],_,[])
    【解决方案3】:

    如果您尝试从功能角度考虑,那么也许可以考虑根据您的左旋转来实现右旋转:

    rightrotate( List, 0 ) ->
      List;
    rightrotate( List, Times ) ->
      lists:reverse( leftrotate( lists:reverse( List ), Times ) ).
    

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

    【讨论】:

      【解决方案4】:

      如果您需要更改项目顺序(如轮换),您的实施将不会高效,因为列表不是正确的表示。 (想象一个有数千个作业的循环调度程序,在完成后将前面的作业放在最后。)

      所以我们实际上只是在问自己,无论如何,在列表上执行此操作的开销最小的方法是什么。但是,什么才是我们想要摆脱的开销呢?人们通常可以通过使用(分配)更多对象来节省一点计算量,或者反过来。在计算过程中,人们通常也可以拥有比需要更大的 live-set 并以这种方式节省分配。


      first_last([First|Tail]) ->
        put_last(First, Tail).
      
      put_last(Item, []) ->
        [Item];
      put_last(Item, [H|Tl]) ->
        [H|put_last(Item,Tl)].
      

      忽略带有空列表等的极端情况;上面的代码将直接使用最终的结果列表。分配的垃圾很少。最终列表是在堆栈展开时构建的。代价是我们在这个操作过程中需要更多的内存来存储整个输入列表和正在构建的列表,但这是一个短暂的短暂的事情。我对 Java 和 Lisp 的伤害使我不得不去优化多余的 consing,但是在 Erlang 中,你不会冒着杀死所有实时属性梦想的全局完整 GC 的风险。无论如何,我一般都喜欢上面的方法。


      last_first(List) ->
        last_first(List, []).
      
      last_first([Last], Rev) ->
        [Last|lists:reverse(Rev)];
      last_first([H|Tl], Rev) ->
        last_first(Tl, [H|Rev]).
      

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

      【讨论】:

      • 这种方法确实很好很干净,但不能推广到旋转 N 个元素(如问题中所示)。当然,您可以将这些功能应用 N 次,但随后您就超出了效率图表。
      • 是的。我没有注意到他想要轮换计数。
      【解决方案5】:

      对您的代码的快速注释。我会更改您调用 append 的函数的名称。在函数式上下文中,追加通常意味着将一个新列表添加到列表的末尾,而不仅仅是一个元素。增加混乱是没有意义的。

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

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

      【讨论】:

        【解决方案6】:

        左:

        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
        

        【讨论】:

        • 好吧,权利似乎比功能更重要。对链表感到羞耻(他们甚至不知道它们的长度)! :)
        • erlang:length(List), lists:split(L - R, List) 是 BIF,我根本不想使用(对于本练习)。有什么建议吗?
        • 长度(L)-> 长度(L,0)。长度([],N)-> N;长度([_|T],N)-> 长度(T,N+1)。 lists:split 不是 BIF(无论定义如何)。打开lists.erl,看看它是如何实现的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-14
        • 1970-01-01
        • 2018-08-23
        • 1970-01-01
        相关资源
        最近更新 更多