Erlang中的递归列表分析

时间:2016-09-16 08:55:51

标签: erlang s-expression

我正在玩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并将其作为参数传递。 我是朝着正确的方向前进的吗?

2 个答案:

答案 0 :(得分:5)

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

-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"]

答案 1 :(得分:3)

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

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

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

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

  • 但是如果我们遇到"(",我们应该开始一个包含子列表的新列表,我们必须在结果中嵌套。在这种情况下,我们需要一个包含列表是我们可以建立新的子列表,以及当我们遇到"("。

  • 时正在进行的列表

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

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

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

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"]