标题^有点令人困惑,但我会说明我想要实现的目标:
我有:
[{<<"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, [<List>]}, {id2, [<List>]} ]
其中 id 是原始列表元组的第二项
例:
<<"5b71d7e458c37fa04a7ce768">>,<<"5b3f77502dfe0deeb8912b42">>,<<"1538077790705827">>
这里的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.
我想我明白你在追求什么......如果我错了,请纠正我。
有很多方法可以做到这一点,这实际上只是取决于您有兴趣使用哪种数据结构来检查 like-keys 是否存在。我将向您展示两种根本不同的方法来执行此操作,以及最近可用的第三种混合方法:
- 索引数据类型(在本例中为映射(
- 列出具有匹配项的操作
- 通过地图键进行混合匹配
由于您是新手,我将使用第一种情况来演示两种编写方法:显式递归和使用 lists 模块中的实际列表函数。
索引数据类型
我们这样做的第一种方法是使用哈希表(又名"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_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.
这里有一行代码可以产生预期的结果:
[{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 <- List]
(,因为 LC 中的生成器为变量引入了新的作用域。
最后,把所有的东西放在一起...
[{K, Filter(K, List)} || K <- Keys]
这实际上取决于您的数据集。对于较大的数据集,使用地图的效率更高一些。
-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