【问题标题】:Recursive list analysis in ErlangErlang中的递归列表分析
【发布时间】:2016-09-16 08:55:51
【问题描述】:

我正在使用 Erlang 并尝试编写一个 S 表达式解析器。我发现在 Python 中使用堆栈和循环是一项简单的任务,但对于我作为不可变变量和 Erlang 数据结构的初学者来说,这并非易事。

我需要像这样在 Erlang 中转换一个列表:

X = ["0", "(", "1", "2", "3", ")"],
Res = transform(X). % ["0", ["1", "2", "3"]]

到目前为止,我已经做到了:

transform(List) ->
    lists:map(fun(X)->
                      case string:equal("(", X) of
                          %% recursive call with sublist of List from "(" to ")" as argument
                          true -> transform_to_list(Lack) 
                      end
              end, List).

不确定如何获取子列表Lack 并将其作为参数传递。 我的方向正确吗?

【问题讨论】:

    标签: erlang s-expression


    【解决方案1】:

    您可以使用累加器和模式匹配来解决这个问题:

    -module(t).
    -export([transform/1]).
    
    transform(List) ->
        transform(List, []).
    
    transform([], Acc) ->
        lists:reverse(Acc);
    transform(["("|T], Acc) ->
        transform(T, {[],Acc});
    transform([")"|T], {L,{L2,Acc}}) ->
        transform(T, {[lists:reverse(L)|L2],Acc});
    transform([")"|T], {L,Acc}) ->
        transform(T, [lists:reverse(L)|Acc]);
    transform([H|T], {L,Acc}) ->
        transform(T, {[H|L],Acc});
    transform([H|T], Acc) ->
        transform(T, [H|Acc]).
    

    transform/1 函数只是为transform/2 设置了一个空累加器,所有的工作都在这里完成。

    transform/2 函数被分成多个模式匹配的递归子句:

    • 第一个子句处理我们已经用尽输入列表的情况,它只是返回反向累加器。需要反转,因为项目被推入累加器,所以它以相反的顺序结束。这是 Erlang 和其他函数式语言中的常见模式。

    • 第二个子句识别"(",它启动一个新的子列表。为了处理它,它将累加器更改为 2 元组,其中第一项是子列表累加器,第二项是旧累加器。

    • 第三个和第四个子句处理")",它结束一个子列表。第三个条款适用于累加器是一个元组的情况,该元组包含第二个元素,该元素也是一个元组;它将新的子列表作为项目添加到前一个子列表中,并从累加器元组中弹出一个级别。第四个子句处理元组中的原始累加器是列表的情况,将新的子列表添加到原始累加器的头部以形成新的累加器列表。

    • 第五和第六子句处理不是分组运算符的输入项。第五个子句处理累加器为元组的情况,第六个子句处理累加器为列表的情况。

    在您的原始示例上运行它会显示正确答案:

    1> c(t).
    {ok,t}
    2> t:transform(["0", "(", "1", "2", "3", ")"]).
    ["0",["1","2","3"]]
    

    但它也可以处理嵌套组:

    3> t:transform(["0", "(", "11", "22", "(", "333", "444",
                    "(", "5555", ")", "666", ")", "77", "88", ")", "9"]).
    ["0",["11","22",["333","444",["5555"],"666"],"77","88"],"9"]
    

    【讨论】:

    • 感谢完美的解释!
    【解决方案2】:

    我知道你已经得到了答案,但是我昨天去海滩之前读了你的问题,我在看风筝冲浪“芭蕾舞”时想象了这个问题,所以我给它,它有点不同比史蒂夫一个,所以它可能会很有趣。

    lists:map 函数不能用于此分析,因为它仅将给定函数应用于列表的每个元素以构建具有相同长度的新列表。没有办法建立嵌套列表。正如@Steve 所说,您需要一个累加器来逐步构建结果。

    lists 库提供了一个在遍历列表时累积术语的函数:lists:foldl/3(它也存在 foldr、mapfoldl 和 mapfoldr),在这种情况下,问题是定义累加器来帮助我们构建预期结果。

    • 要分析的最简单的列表没有括号,因此累加器应该包含一个列表,用于累积条目列表的所有元素。

    • 但是如果我们遇到“(”,我们应该开始一个新的列表,其中包含我们必须嵌套在结果中的子列表。在这种情况下,我们需要一个包含列表的术语要构建的新子列表,以及遇到“(”时正在进行的列表。

    可以在一个表单中满足这两个需求的最简单结构是列表列表:[SublistInProgress|PreviousWork]

    现在我们知道了累加器的形式,我们可以定义负责构建它的函数,3种情况:

    • 我们找到一个“(”:开始一个新的子列表,并“存储”之前的累加器
    • 我们找到一个“)”:将子列表添加到上一个累加器中
    • 在任何其他情况下,将元素添加到正在进行的子列表中。

    在外壳中:

    1>  F = fun("(",Acc)-> [[],Acc];                                               
    1>         (")",[SubList,[Hacc|Tacc]]) -> [[lists:reverse(SubList)|Hacc]|Tacc];
    1>         (X,[Hacc|Tacc]) -> [[X|Hacc]|Tacc] end.                             
    #Fun<erl_eval.12.52032458>
    

    注意:我使用构造[X|Hacc]而不是Hacc ++ [X]来累积列表中的元素,这是一个好习惯,因为它避免在每一步都创建一个全新的列表(这样做我会避免备注来自我的朋友@Hynek-Pichi-Vychodil :o)。所以当我想存储它时,我必须反转列表。

    在函数lists:foldl(F,[[]],L)中使用F,我们将得到一个元素的列表,这个元素与预期结果相反。所以我们必须将这个对库的调用嵌入到一个特定的函数中:

    2> Transform = fun(L) -> [R] = lists:foldl(F,[[]],L),
    2>                       lists:reverse(R) end.
    #Fun<erl_eval.6.52032458>
    

    我们可以测试它:

    3> L1 = ["0", "(", "1", "2", "3", ")"].
    ["0","(","1","2","3",")"]
    4> L2 = ["0", "(", "11", "22", "(", "333", "444","(", "5555", ")", "666", ")", "77", "88", ")", "9"].
    ["0","(","11","22","(","333","444","(","5555",")","666",")",
     "77","88",")","9"]
    5> Transform(L1).
    ["0",["1","2","3"]]
    6> Transform(L2).
    ["0",["11","22",["333","444",["5555"],"666"],"77","88"],"9"]
    

    【讨论】:

    • 漂亮而优雅的解决方案。我会注意的。谢谢!
    猜你喜欢
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多