erlang:从proplists获取键集的值

时间:2015-09-12 14:32:05

标签: list loops search erlang

我有"困难" proplists。我需要到达serviceCatalog并遍历其所有值,直到我得到一个名字= me

J =[{<<"access">>,
      {[{<<"token">>,
         {[{<<"issued_at">>,<<"2015-09-12T13:27:38.789879">>},
           {<<"expires">>,<<"2015-09-12T14:27:38Z">>},
           {<<"id">>,<<"fe">>},
           {<<"tenant">>,
            {[{<<"description">>,null},
              {<<"enabled">>,true},
              {<<"id">>,<<"01">>},
              {<<"name">>,<<"service">>}]}},
           {<<"audit_ids">>,[<<"f">>]}]}},
        {<<"serviceCatalog">>,
         [{[{<<"endpoints">>,
             [{[{<<"adminURL">>,<<"http://8.198.99.999:8080">>},
                {<<"region">>,<<"RegionOne">>},
                {<<"internalURL">>,
                 <<"http://8.198.99.999:8080/v1/AUTH_01a"...>>},
                {<<"id">>,<<"30">>},
                {<<"publicURL">>,<<"8.198.99.999:8080/v1/"...>>}]}]},
            {<<"endpoints_links">>,[]},
            {<<"type">>,<<"object-store">>},
            {<<"name">>,<<"my">>}]},
          {[{<<"endpoints">>,
             [{[{<<"adminURL">>,<<"8.198.99.999:8080/v2.0">>},
                {<<"region">>,<<"RegionOne">>},
                {<<"internalURL">>,<<"8.198.99.999:8080/v2.0">>},
                {<<"id">>,<<"4b3f44a5c64b4bd8b10c376c858b"...>>},
                {<<"publicURL">>,<<"8.198.99.999:8080"...>>}]}]},
            {<<"endpoints_links">>,[]},
            {<<"type">>,<<"identity">>},
            {<<"name">>,<<"other">>}]}]},
        {<<"user">>,
         {[{<<"username">>,<<"my">>},
           {<<"roles_links">>,[]},
           {<<"id">>,<<"8">>},
           {<<"roles">>,[{[{<<"name">>,<<"admin">>}]}]},
           {<<"name">>,<<"me">>}]}},
        {<<"metadata">>,
         {[{<<"is_admin">>,0},
           {<<"roles">>,
            [<<"e">>]}]}}]}}]

我正在寻找这样做的好方法。 我开始这样的实现(但看起来有更优雅的解决方案):

A1=proplists:get_value(<<"access">>,J).
A=element(1,A1).
B=proplists:get_value(<<"token">>, A).
C=element(1,B).
D=proplists:get_value(<<"serviceCatalog">>, C).

任何建议都很好

1 个答案:

答案 0 :(得分:1)

考虑到数据的嵌套程度如何,一种方法是编写一组递归函数子句,这些子句可以找到具有给定键的2元组,而不管其在数据中的级别如何。这是一个例子:

-module(t).
-export([extract/2]).

extract(Key, Data) ->
    extract(Key, Data, []).
extract(_Key, [], Acc) ->
    Acc;
extract(Key, {Key,_}=KV, Acc) ->
    [KV|Acc];
extract(Key, [{Key,_}=KV|Fields], Acc) ->
    extract(Key, Fields, [KV|Acc]);
extract(Key, [{_,V}|Fields], Acc) when is_tuple(V); is_list(V) ->
    extract(Key, Fields, extract(Key, V, Acc));
extract(Key, Data, Acc) when is_list(Data) ->
    lists:foldl(fun(V, FoldAcc) when is_tuple(V); is_list(V) ->
                        extract(Key, V, FoldAcc);
                   (_, FoldAcc) ->
                        FoldAcc
                end, Acc, Data);
extract(Key, Data, Acc) when is_tuple(Data) ->
    extract(Key, tuple_to_list(Data), Acc).

extract/2函数只将其参数和空累加器列表传递给extract/3extract/3的条款如下所述:

  • 第一个子句检查数据是否为空列表,如果是,则返回累加器。
  • 第二个子句匹配所需的Key作为2元组的第一个元素,并将匹配的元组添加到累加器。
  • 第三个子句匹配所需的Key作为列表头部的2元组的第一个元素。它补充说,将元组与累加器匹配,并继续从列表的其他元素中提取。
  • 第四个子句处理2元组的情况,在列表的头部没有匹配的键,其中元组中的值是列表或元组;如果是这样,它会尝试从值中提取,然后继续从列表的其余部分中提取。
  • Data是一个与之前的子句已经匹配的列表不匹配的列表时,第五个子句匹配。它折叠列表的所有值,尝试从所有嵌套列表和元组值中提取并忽略所有其他值。
  • Data是元组时,第六个和最后一个子句匹配;它只是将元组转换为列表并递归调用extract/3

根据问题中定义的数据J,我们可以找到一个带有<<"serviceCatalog">>键的2元组,如下所示:

1> SCs = t:extract(<<"serviceCatalog">>, J).
[{<<"serviceCatalog">>,
  [{[{<<"endpoints">>,
      [{[{<<"adminURL">>,<<"http://8.198.99.999:8080">>},
         {<<"region">>,<<"RegionOne">>},
         {<<"internalURL">>,
          <<"http://8.198.99.999:8080/v1/AUTH_01a">>},
         {<<"id">>,<<"30">>},
         {<<"publicURL">>,<<"8.198.99.999:8080/v1/">>}]}]},
     {<<"endpoints_links">>,[]},
     {<<"type">>,<<"object-store">>},
     {<<"name">>,<<"my">>}]},
   {[{<<"endpoints">>,
      [{[{<<"adminURL">>,<<"8.198.99.999:8080/v2.0">>},
         {<<"region">>,<<"RegionOne">>},
         {<<"internalURL">>,<<"8.198.99.999:8080/v2.0">>},
         {<<"id">>,<<"4b3f44a5c64b4bd8b10c376c858b">>},
         {<<"publicURL">>,<<"8.198.99.999:8080">>}]}]},
     {<<"endpoints_links">>,[]},
     {<<"type">>,<<"identity">>},
     {<<"name">>,<<"other">>}]}]}]

提取物找到了我们期望的元素。然后,我们可以将其传递给t:extract/2,以查看我们找到的元素是否包含我们正在寻找的<<"name">>元素。我们将使用列表推导来返回元组列表,每个元组包含一个<<"serviceCatalog">>元素和一个布尔值,指示它是否包含所需的<<"name">>元素:

2> [{SC,lists:any(fun({_, V}) -> V == <<"me">> end, t:extract(<<"name">>, SC))} || SC <- SCs].

列表推导遍历所有<<"serviceCatalog">>元素,从每个元素中提取所有<<"name">>元组并检查值为<<"me">>的任何元组。

不幸的是,示例数据中的<<"serviceCatalog">>元素没有问题中描述的{<<"name">>, <<"me">>}元组,因此上面列表理解结果中的布尔值(结果未显示)是false。但无论如何,为了遍历列表理解的结果,只保留true值,但删除布尔值,以便结果列表仅包含匹配的<<"serviceCatalog">>元素,我们可以折叠列表。首先,让我们做一个折叠函数:

3> Fold = fun({SC, true}, Acc) -> [SC|Acc]; (_, Acc) -> Acc end.
#Fun<erl_eval.12.54118792>

我们的fold函数接受每个{SC, boolean}元组,将具有true值的元素添加到累加器列表中,并删除具有false值的元组。下面我们从前面捕获列表推导的结果,然后将它和折叠函数传递给lists:foldl/3

4> SCBools = [{SC,lists:any(fun({_, V}) -> V == <<"me">> end, t:extract(<<"name">>, SC))} || SC <- SCs].
...
5> lists:foldl(Fold, [], SCBools).
[]

同样,由于数据中没有<<"serviceCatalog">>个元素包含{<<"name">>, <<"me">>}元组,因此此处的结果列表为空,但如果有的话,我们会在返回的列表中获取它们的值lists:foldl/3