【问题标题】:Flatten a list of nested lists in Erlang在 Erlang 中展平嵌套列表的列表
【发布时间】:2012-03-09 19:53:31
【问题描述】:

我正在做 Erlang Programming 中的练习。

问题是

编写一个函数,给定一个嵌套列表,将返回一个平面列表。

示例: flatten([[1,[2,[3],[]]], [[[4]]], [5,6]]) ⇒ [1,2,3,4,5,6].

提示:使用concatenate解决flatten

这是我的concatenate 函数

%% concatenate([[1,2,3], [], [4, five]]) ⇒ [1,2,3,4,five].
concatenate([X|Xs]) -> concat(X, Xs, []).
concat([X|Xs], T, L) -> concat(Xs, T, [X|L]);
concat([], [X|Xs], L) -> concat(X, Xs, L);
concat([], [], L) -> reverse(L).

我真的很想知道一种优雅的方式来实现flatten。我花了几个小时来解决这个练习。

更新:我忘记了最重要的先决条件。是否可以仅通过递归模式匹配来解决这个问题?

【问题讨论】:

    标签: list erlang


    【解决方案1】:

    问题的关键是“分而治之”。

    另一个额外的函数“lists:reverse”和一个运算符“++”用于节省编程时间。

    
    
    my_flat([],Result)->
        lists:reverse(Result);
    my_flat([H|T],Result) when is_atom(H) ->
        case T of
        []-> 
            my_flat([],[H|Result]);
        _Else ->
            my_flat(T,[H|Result])
        end;
    my_flat([H|T],Result) when is_number(H)->
        case T of
        []->
            my_flat([],[H|Result]);
        _Else ->
            my_flat(T,[H|Result])
        end;
    my_flat([H|T],Result) ->
    my_flat(H,Result)++my_flat(T,[]).
    

    对于您的测试:test:my_flat([[1,[2,[3],[]]], [[[4]]], [5,6]],[])。

    【讨论】:

      【解决方案2】:

      我会尝试这种方式

      flatten(X) -> lists:reverse(flatten(X,[])).
      
      flatten([],Acc) -> Acc;
      flatten([H|T],Acc) when is_list(H) -> flatten(T, flatten(H,Acc));
      flatten([H|T],Acc) -> flatten(T,[H|Acc]).
      

      测试

      my:flatten([[1,[2,[3],[]]], [[[4]]], [5,6]]).
      [1,2,3,4,5,6]
      

      UPD:或者这种方式,没有保护和反向,只有递归调用和模式匹配。

      flatten(X)               -> flatten(X,[]).
      
      flatten([],Acc)          -> Acc;
      flatten([[]|T],Acc)      -> flatten(T, Acc);
      flatten([[_|_]=H|T],Acc) -> flatten(T, flatten(H,Acc));
      flatten([H|T],Acc)       -> flatten(T,Acc++[H]) .
      

      【讨论】:

      • 只用递归和模式匹配就可以解决这个问题吗?
      • 这个提示完全误导了我。谢谢!
      • 使用++ 效率低,因为它会复制整个列表。
      • @rvirding 没错。这就是为什么在现实生活中 a 更喜欢第一个变体。但是作为练习++是可以的。
      【解决方案3】:

      一些不同的解决方案,变得越来越聪明:

      %% Lift nested lists to the front of the list.
      flatten1([[H|T1]|T2]) -> flatten1([H,T1|T2]);
      flatten1([[]|T]) -> flatten1(T);
      flatten1([E|T]) -> [E|flatten1(T)];
      flatten1([]) -> [].
      

      %% Keep a list of things todo and put tails onto it.
      flatten2(L) -> flatten2(L, []).
      
      flatten2([H|T], Todo) ->
          flatten2(H, [T|Todo]);
      flatten2([], [H|Todo]) -> flatten2(H, Todo);
      flatten2([], []) -> [];
      flatten2(E, Todo) -> [E|flatten2(Todo, [])].
      

      %% Work from the back and keep a tail of things done.
      flatten3(L) -> flatten3(L, []).
      
      flatten3([H|T], Tail) ->
          flatten3(H, flatten3(T, Tail));
      flatten3([], Tail) -> Tail;
      flatten3(E, Tail) -> [E|Tail].
      

      这些都只有模式匹配和递归,但可以通过一些保护类型测试来改进。使用++ 效率低下,因为它每次都复制列表。 lists 模块使用带有保护类型测试的最后一个版本,而不是最后一个子句。

      【讨论】:

      • 我找到了this。那么什么时候应该使用 ++ 运算符呢?
      • 现在我认为使用列表推导和 ++ 运算符来实现快速排序不是一个好主意:(
      • @Vayn 您应该尽量避免使用++ 或任何其他方式将元素附加到列表的末尾。追加不是列表的最佳操作。将两个列表连接在一起是另一回事。绕过它的一种方法是像我的第三个示例中那样携带尾巴。
      【解决方案4】:

      相当简洁明了的版本:

      append([H | T], L) -> [H | append(T, L)];
      append([], L) -> L.
      
      flatten([[_|_]=H|T]) -> append(flatten(H), flatten(T));
      flatten([[]|T]) -> flatten(T);
      flatten([H|T]) -> [H|flatten(T)];
      flatten([]) -> [].
      

      【讨论】:

      • 谢谢。在看到其他解决方案以不好的方式使用 ++ 之后,我正要自己提供这个解决方案。
      • @DanielLuna,追加相当于 ++
      • @OdobenusRosmarus:++append 都不是问题,但 使用不当 是。我将append 与扁平头部的遍历一起应用,但您正在使用它来增长Acc,这是非常错误的。我认为即使是运动也是错误的。
      • @Hynek-Pichi-Vychodil 1) 如果没有上下文,计算 cpu 滴答声就没有任何意义——这只是过早的优化。 2) 但是如果你想计算滴答声 - 只需跟踪你和我的程序,例如数据 [1,[2,[3,4,5]]],你会看到 ++ 用短数据调用 5 次,并且您的 append() 调用 9 次(包括带有完整列表的案例)。 3)(最后但并非最不重要的)——漂亮的昵称:-)
      • @OdobenusRosmarus:当您可以轻松地以线性方式执行 ti 时,以二次方式实现某些东西简直是愚蠢的事情。这不是关于计算时钟滴答声,而是关于算法,与'erlang VM implementation 的特殊性'没有任何共同之处。
      【解决方案5】:

      concatenate/1 如书中定义的那样作为一个展平函数,它只展平一个级别。 ([[1],[2]] 变为 [1,2][[[1]],[[2]]] 变为 [[1],[2]] 等)提示中建议的策略是完全展平,不是通过在 flatten/1 中定义新逻辑,而是在 flatten/1 的递归中使用 concatenate/1来电。

      concatenate(Ls) ->
          reverse(concatenate(Ls, [])).
      
      concatenate([], Acc) ->
          Acc;
      concatenate([[]|Rest], Acc) ->
          concatenate(Rest, Acc);
      concatenate([[H|T]|Rest], Acc) ->
          concatenate([T|Rest], [H|Acc]);
      concatenate([H|T], Acc) ->
          concatenate(T, [H|Acc]).
      
      flatten(L) ->
          flatten(L, []).
      
      flatten([], Acc) ->
          Acc;
      flatten(L, Acc) ->
          Concatted = concatenate(L),
          [Non_lists|Remainder] = find_sublist(Concatted),
          flatten(Remainder, concatenate([Acc, Non_lists])).
      
      find_sublist(L) ->
          find_sublist(L, []).
      
      find_sublist([], Acc) ->
          reverse(Acc);
      find_sublist(L = [[_|_]|_], Acc) ->
          [reverse(Acc)|L];
      find_sublist([H|T], Acc) ->
          find_sublist(T, [H|Acc]).
      
      tests() ->
          [1,2,3,4,4,5,6,7,8] = flatten([[1,[2,[3],[]]], [[[4,[4]]]], [[5],6], [[[]]], [], [[]], [[[7, 8]]]]),
          [1,2] = flatten([[1,2]]),
          [1,2,3] = flatten([[1],[2],[3]]),
          [1,2,3,4,5,6] = flatten([[1,[2,[3],[]]], [[[4]]], [5,6]]),
          tests_successful.
      

      【讨论】:

        猜你喜欢
        • 2020-10-11
        • 2018-07-04
        • 1970-01-01
        • 1970-01-01
        • 2015-04-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-05
        相关资源
        最近更新 更多