【问题标题】:List of tuples [{id, [<List>]}, {id2, [<List>]} ] where ids are the second item of the tuple of the original list- Erlang元组列表 [{id, [<List>]}, {id2, [<List>]} ] 其中 id 是原始列表元组的第二项 - Erlang
【发布时间】:2018-09-29 13:44:53
【问题描述】:

标题^有点令人困惑,但我会说明我想要实现的目标:

我有:

[{<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077790705827">>},
     {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538078530667847">>},
     {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077778390908">>},
     {<<"5b71d7e458c37fa04a7ce768">>,<<"5bad45b1e990057961313822">>,<<"1538082492283531">>
}]

我想把它转换成这样的列表:

[ 
{<<"5b3f77502dfe0deeb8912b42">>,
   [{<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077790705827">>},
    {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538078530667847">>},
    {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077778390908">>}
   ]},

{<<"5bad45b1e990057961313822">>,
   [{<<"5b71d7e458c37fa04a7ce768">>,<<"5bad45b1e990057961313822">>,<<"1538082492283531">>}
   ]}
]

元组列表[{id, [&lt;List&gt;]}, {id2, [&lt;List&gt;]} ],其中id是原始列表元组的第二项

例子:

>,>,>

【问题讨论】:

  • 你能展示你尝试过的东西吗?哪个部分不工作?

标签: erlang


【解决方案1】:

这里是 Erlang 新手。我创建了一个dict,其中元组的第二个成员作为键,对应元组的列表作为值,然后使用dict:fold 将其转换为预期的输出格式。

-export([test/0, transform/1]).

transform([H|T]) ->
    transform([H|T], dict:new()).

transform([], D) ->
    lists:reverse(
      dict:fold(fun (Key, Tuples, Acc) ->
                        lists:append(Acc,[{Key,Tuples}])
                end,
                [],
                D));
transform([Tuple={_S1,S2,_S3}|T], D) ->
    transform(T, dict:append_list(S2, [Tuple], D)).

test() ->
    Input=[{<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077790705827">>},
           {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538078530667847">>},
           {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077778390908">>},
           {<<"5b71d7e458c37fa04a7ce768">>,<<"5bad45b1e990057961313822">>,<<"1538082492283531">>}
          ],
    Output=transform(Input),
    case Output of
        [ 
          {<<"5b3f77502dfe0deeb8912b42">>,
           [{<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077790705827">>},
            {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538078530667847">>},
            {<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077778390908">>}
           ]},
          {<<"5bad45b1e990057961313822">>,
           [{<<"5b71d7e458c37fa04a7ce768">>,<<"5bad45b1e990057961313822">>,<<"1538082492283531">>}
           ]}
        ]     -> ok;
        _Else -> error
    end.

【讨论】:

    【解决方案2】:

    我想我明白你在追求什么......如果我错了,请纠正我。

    有很多方法可以做到这一点,这实际上取决于您有兴趣使用哪种数据结构来检查是否存在类似键。我将向您展示两种完全不同的方法以及最近出现的第三种混合方法:

    • 索引数据类型(在本例中为map
    • 使用matching列出操作
    • 基于映射键的混合匹配

    由于您是新手,我将使用第一种情况来演示两种编写方式:显式递归和使用列表模块中的an actual list function

    索引数据类型

    我们这样做的第一种方法是使用哈希表(又名“dict”、“map”、“hash”、“K/V”等)并显式递归遍历元素,检查遇到的键是否存在,如果它丢失则添加它,或者如果它存在,则附加到它指向的值列表中。我们将为此使用 Erlang 映射。在函数结束时,我们会将实用程序映射转换回列表:

    explicit_convert(List) ->
        Map = explicit_convert(List, maps:new()),
        maps:to_list(Map).
    
    explicit_convert([H | T], A) ->
        K = element(2, H),
        NewA =
            case maps:is_key(K, A) of
                true ->
                    V = maps:get(K, A),
                    maps:put(K, [H | V], A);
                false ->
                    maps:put(K, [H], A)
            end,
        explicit_convert(T, NewA);
    explicit_convert([], A) ->
        A.
    

    显式递归并没有什么问题(如果你是新手,这特别好,因为它的每个部分都留待检查),但这是一个“左折叠”,我们已经有一个库抽象出一点管道的函数。所以我们真的只需要编写一个函数来检查元素是否存在,并添加键或附加值:

    fun_convert(List) ->
        Map = lists:foldl(fun convert/2, maps:new(), List),
        maps:to_list(Map).
    
    convert(H, A) ->
        K = element(2, H),
        case maps:is_key(K, A) of
            true ->
                V = maps:get(K, A),
                maps:put(K, [H | V], A);
            false ->
                maps:put(K, [H], A)
        end.
    

    Listy 转换

    我们可以做到这一点的另一个主要方法是使用列表匹配。要做到这一点,您首先需要保证您的元素在您想要用作键的元素上进行排序,以便您可以将其用作一种“工作元素”并在其上进行匹配。一旦你盯着它看一会儿,代码应该很容易理解(如果你完全感到困惑,也许可以在纸上写下它会如何手动遍历你的列表):

    listy_convert(List) ->
        [T = {_, K, _} | Rest] = lists:keysort(2, List),
        listy_convert(Rest, {K, [T]}, []).
    
    listy_convert([T = {_, K, _} | Rest], {K, Ts}, Acc) ->
        listy_convert(Rest, {K, [T | Ts]}, Acc);
    listy_convert([T = {_, K, _} | Rest], Done, Acc) ->
        listy_convert(Rest, {K, [T]}, [Done | Acc]);
    listy_convert([], Done, Acc) ->
        [Done | Acc].
    

    请注意,我们在排序后立即拆分列表。原因是我们在第一次致电listy_convert/3 时“启动了泵”。这也意味着如果你传递一个空列表,这个函数将会崩溃。您可以通过向listy_convert/1 添加一个与空列表[] 匹配的子句来解决这个问题。

    最后一点魔法

    牢牢记住这些......考虑到我们在新版本的 Erlang 中也有一些混合选项可用,因为地图可用的神奇语法。我们可以在 case 子句内的映射键上匹配(大多数值)(尽管我们无法统一函数头中其他参数提供的键值):

    map_convert(List) ->
        maps:to_list(map_convert(List, #{})).
    
    map_convert([T = {_, K, _} | Rest], Acc) ->
        case Acc of
            #{K := Ts} -> map_convert(Rest, Acc#{K := [T | Ts]});
            _          -> map_convert(Rest, Acc#{K => [T]})
        end;
    map_convert([], Acc) ->
        Acc.
    

    【讨论】:

      【解决方案3】:

      这是一个可以产生预期结果的单行代码:

      [{K, [E || {_, K2, _} = E <- List, K =:= K2]}  || {_, K, _} <- lists:ukeysort(2, List)].
      

      这里发生了什么?让我们一步一步来……

      这是您的原始列表

      List = […],
      

      lists:ukeysort/2 在列表中每个键只留下一个元素

      OnePerKey = lists:ukeysort(2, List),
      

      然后我们用第一个列表推导提取键

      Keys = [K || {_, K, _} <- OnePerKey],
      

      通过第二个列表推导,我们找到带有键的元素……

      fun Filter(K, List) ->
        [E || {_, K2, _} = E <- List, K =:= K2]
      end
      

      请记住,我们不能只与生成器中的 K(即 [E || {_, K, _} = E &lt;- List])进行模式匹配,因为 LC 中的生成器 introduce new scope for the variables

      最后,把所有东西放在一起……

      [{K, Filter(K, List)} || K <- Keys]
      

      【讨论】:

      • 我唯一不喜欢的就是 O(n^2) 问题——对于外部列表理解中的每个项目,内部列表推导遍历整个原始列表一次具有初始(不可预测的)遍历以实现 ukeysort。 (而且我不确定 OP 的问题是我们真的想要一个 keysort 还是一个 ukeysort ......?没有讨论重复,所以这对我来说有点不清楚。)
      • 比大多数实现更快,但地图更快。
      【解决方案4】:

      这真的取决于您的数据集。对于较大的数据集,使用地图会更有效。

      -module(test).
      -export([test/3, v1/2, v2/2, v3/2, transform/1, do/2]).
      
      
      test(N, Keys, Size) ->
          List = [{<<"5b71d7e458c37fa04a7ce768">>,rand:uniform(Keys),<<"1538077790705827">>} || I <- lists:seq(1,Size)],
      
      V1 = timer:tc(test, v1, [N, List]),
      V2 = timer:tc(test, v2, [N, List]),
      V3 = timer:tc(test, v3, [N, List]),
      io:format("V1 took: ~p, V2 took: ~p V3 took: ~p ~n", [V1, V2, V3]).
      
      
      v1(N, List) when N > 0 ->
        [{K, [E || {_, K2, _} = E <- List, K =:= K2]}  || {_, K, _} <- lists:ukeysort(2, List)],
        v1(N-1, List);
      v1(_,_) -> ok.
      
      v2(N, List) when N > 0 ->
        do(List,maps:new()),
        v2(N-1, List);
      v2(_,_) -> ok.
      
      v3(N, List) when N > 0 ->
        transform(List),
        v3(N-1, List);
      v3(_,_) -> ok.
      
      do([], R) -> maps:to_list(R);
      
      do([H={_,K,_}|T], R) ->
        case maps:get(K,R,null) of
          null -> NewR = maps:put(K, [H], R);
          V -> NewR = maps:update(K, [H|V], R)
        end,
        do(T, NewR).
      
      
      
      transform([H|T]) ->
        transform([H|T], dict:new()).
      
      transform([], D) ->
        lists:reverse(
          dict:fold(fun (Key, Tuples, Acc) ->
                          lists:append(Acc,[{Key,Tuples}])
                  end,
                  [],
                  D));
      transform([Tuple={_S1,S2,_S3}|T], D) ->
        transform(T, dict:append_list(S2, [Tuple], D)).
      

      同时运行 100 个唯一键和 100,000 条记录我得到:

      > test:test(1,100,100000).
      V1 took: {75566,ok}, V2 took: {32087,ok} V3 took: {887362,ok} 
      ok
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-29
        • 2018-04-07
        相关资源
        最近更新 更多