右键在Erlang中旋转一个List

时间:2009-10-09 16:34:22

标签: erlang functional-programming

我现在熟悉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可以提供一些功能来提高功能编程不擅长的效率(或者如果我们以'功能方式'执行它们,性能不是最佳的)。

6 个答案:

答案 0 :(得分:3)

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

其次,我会建议你这样的事情:

-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}}.

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

答案 1 :(得分:3)

你提到的效率问题与过度递归(函数调用很便宜)无关,而且与步行和重建列表有关。每次在列表末尾添加内容时,都必须遍历并复制整个列表,这可以从append的实现中看出来。因此,要旋转列表,N步骤需要我们将整个列表复制N次。我们可以使用列表: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]).

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

答案 2 :(得分:2)

如果你试图用功能术语思考,那么也许可以考虑在你的左旋转方面实现正确的旋转:

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

不是说这是最好的主意或任何事情:)

答案 3 :(得分:2)

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

因此,我们实际上只是问自己,无论如何,在列表上执行此操作的开销最小。但那么我们想摆脱的开销是什么呢?人们通常可以通过获取(分配)更多对象来节省一些计算,或者相反。在计算过程中,人们通常也可能拥有比实际需要更大的活动集,并以这种方式保存分配。


first_last([First|Tail]) ->
  put_last(First, Tail).

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

忽略带有空列表等的角落案例;上面的代码将直接包含最终结果列表。分配的垃圾很少。最终列表是在堆栈展开时构建的。成本是我们在整个输入列表和构造中的列表需要更多的内存,但这是一个短暂的瞬间。我对Java和Lisp造成的破坏使我能够优化下来过多的消耗,但在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的临时列表,在我们将它传递给列表后将其丢弃:reverse / 1(它调用BIF列表:reverse / 2,但它没有做任何有趣的事情)。通过创建此临时反转列表,我们避免必须遍历列表两次。一旦建立一个包含除最后一个项目之外的所有内容的列表,再一次用于获取最后一个项目。

答案 4 :(得分:1)

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

如上所述:split不是BIF,它是用erlang编写的库函数。 BIF究竟是什么,没有正确定义。

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

答案 5 :(得分:0)

左:

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