File 5881-Add-maps-groups_from_list.patch of Package erlang
From 306f8bd8dbba91632d1bec536d7a0288fd1b8ee4 Mon Sep 17 00:00:00 2001
From: Gian Lorenzo Meocci <glmeocci@gmail.com>
Date: Thu, 6 Jan 2022 15:23:32 +0100
Subject: [PATCH] Add maps:groups_from_list
---
lib/stdlib/doc/src/maps.xml | 36 ++++++++++++++-
lib/stdlib/src/erl_stdlib_errors.erl | 4 ++
lib/stdlib/src/maps.erl | 66 +++++++++++++++++++++++++++-
lib/stdlib/test/maps_SUITE.erl | 23 +++++++++-
4 files changed, 125 insertions(+), 4 deletions(-)
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index ea0f16eec9..a8793e86d1 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -606,7 +606,7 @@ error</code>
maps:update_with("new counter",Fun,42,Map).
#{"counter" => 1,"new counter" => 42}</code>
</desc>
- </func>
+ </func>
<func>
<name name="values" arity="1" since="OTP 17.0"/>
@@ -657,5 +657,39 @@ error</code>
#{1337 => "value two"}</code>
</desc>
</func>
+
+ <func>
+ <name name="groups_from_list" arity="2" since="OTP 26.0"/>
+ <fsummary>Splits the list into groups using a function as discriminator.</fsummary>
+ <desc>
+ <p>The result is a map where each key is given by <anno>Fun</anno>
+ and each value is a list of elements. The order of elements within
+ each list is preserved from the list.</p>
+ <p><em>Examples:</em></p>
+ <pre>
+> <input>maps:groups_from_list(fun(X) -> X rem 2 end, [1,2,3]).</input>
+#{0 => [2], 1 => [1, 3]}
+> <input>maps:groups_from_list(fun erlang:length/1, ["ant", "buffalo", "cat", "dingo"]).</input>
+#{3 => ["ant", "cat"], 5 => ["dingo"], 7 => ["buffalo"]}</pre>
+ </desc>
+ </func>
+
+ <func>
+ <name name="groups_from_list" arity="3" since="OTP 26.0"/>
+ <fsummary>Splits the list into groups using a function as discriminator.</fsummary>
+ <desc>
+ <p>The result is a map where each key is given by
+ <anno>Fun</anno> and each value is a list of elements given by
+ the <anno>ValueFun</anno>. The order of elements within each
+ list is preserved from the list.</p>
+ <p><em>Examples:</em></p>
+ <pre>
+> <input>maps:groups_from_list(fun(X) -> X rem 2 end, fun(X) -> X*X end, [1,2,3]).</input>
+#{0 => [4], 1 => [1, 9]}
+> <input>maps:groups_from_list(fun erlang:length/1, fun lists:reverse/1, ["ant", "buffalo", "cat", "dingo"]).</input>
+#{3 => ["tna","tac"],5 => ["ognid"],7 => ["olaffub"]}</pre>
+ </desc>
+ </func>
+
</funcs>
</erlref>
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index dc1152cb39..2306c37bf1 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -225,6 +225,10 @@ format_maps_error(get, [_Key,Map]) ->
true ->
[[],not_map]
end;
+format_maps_error(groups_from_list, [Fun, List]) ->
+ [must_be_fun(Fun, 1), must_be_list(List)];
+format_maps_error(groups_from_list, [Fun1, Fun2, List]) ->
+ [must_be_fun(Fun1, 1), must_be_fun(Fun2, 1), must_be_list(List)];
format_maps_error(get, [_,_,_]) ->
[[],not_map];
format_maps_error(intersect, [Map1, Map2]) ->
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index e6192eb22b..e3fb532750 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -26,7 +26,8 @@
without/2, with/2,
iterator/1, next/1,
intersect/2, intersect_with/3,
- merge_with/3]).
+ merge_with/3,
+ groups_from_list/2, groups_from_list/3]).
%% BIFs
-export([get/2, find/2, from_list/1, from_keys/2,
@@ -497,6 +498,69 @@ with_1([K|Ks], Map) ->
end;
with_1([], _Map) -> [].
+%% groups_from_list/2 & groups_from_list/3
+
+-spec groups_from_list(Fun, List) -> MapOut when
+ Fun :: fun((Elem :: T) -> Selected),
+ MapOut :: #{Selected => List},
+ Selected :: term(),
+ List :: [T],
+ T :: term().
+
+groups_from_list(Fun, List0) when is_function(Fun, 1) ->
+ try lists:reverse(List0) of
+ List ->
+ groups_from_list_1(Fun, List, #{})
+ catch
+ error:_ ->
+ badarg_with_info([Fun, List0])
+ end;
+groups_from_list(Fun, List) ->
+ badarg_with_info([Fun, List]).
+
+groups_from_list_1(Fun, [H | Tail], Acc) ->
+ K = Fun(H),
+ NewAcc = case Acc of
+ #{K := Vs} -> Acc#{K := [H | Vs]};
+ #{} -> Acc#{K => [H]}
+ end,
+ groups_from_list_1(Fun, Tail, NewAcc);
+groups_from_list_1(_Fun, [], Acc) ->
+ Acc.
+
+-spec groups_from_list(Fun, ValueFun, List) -> MapOut when
+ Fun :: fun((Elem :: T) -> Key),
+ ValueFun :: fun((Elem :: T) -> ValOut),
+ MapOut :: #{Key := ListOut},
+ Key :: term(),
+ ValOut :: term(),
+ List :: [T],
+ ListOut :: [ValOut],
+ T :: term().
+
+groups_from_list(Fun, ValueFun, List0) when is_function(Fun, 1),
+ is_function(ValueFun, 1) ->
+ try lists:reverse(List0) of
+ List ->
+ groups_from_list_2(Fun, ValueFun, List, #{})
+ catch
+ error:_ ->
+ badarg_with_info([Fun, ValueFun, List0])
+ end;
+groups_from_list(Fun, ValueFun, List) ->
+ badarg_with_info([Fun, ValueFun, List]).
+
+groups_from_list_2(Fun, ValueFun, [H | Tail], Acc) ->
+ K = Fun(H),
+ V = ValueFun(H),
+ NewAcc = case Acc of
+ #{K := Vs} -> Acc#{K := [V | Vs]};
+ #{} -> Acc#{K => [V]}
+ end,
+ groups_from_list_2(Fun, ValueFun, Tail, NewAcc);
+groups_from_list_2(_Fun, _ValueFun, [], Acc) ->
+ Acc.
+
error_type(M) when is_map(M) -> badarg;
error_type(V) -> {badmap, V}.
diff --git a/lib/stdlib/test/maps_SUITE.erl b/lib/stdlib/test/maps_SUITE.erl
index 28895c4efe..2492130cf1 100644
--- a/lib/stdlib/test/maps_SUITE.erl
+++ b/lib/stdlib/test/maps_SUITE.erl
@@ -42,7 +42,8 @@
t_from_list_check_trapping/1,
t_from_keys_check_trapping/1,
t_keys_trapping/1,
- t_values_trapping/1]).
+ t_values_trapping/1,
+ t_groups_from_list/1]).
-define(badmap(V,F,Args), {'EXIT', {{badmap,V}, [{maps,F,Args,_}|_]}}).
-define(badkey(K,F,Args), {'EXIT', {{badkey,K}, [{maps,F,Args,_}|_]}}).
@@ -68,7 +69,8 @@ all() ->
t_from_list_check_trapping,
t_from_keys_check_trapping,
t_keys_trapping,
- t_values_trapping].
+ t_values_trapping,
+ t_groups_from_list].
t_from_list_kill_process(Config) when is_list(Config) ->
Killer = self(),
@@ -770,6 +772,16 @@ t_size_1(Config) when is_list(Config) ->
{'EXIT', {{badmap,<<>>}, _}} = (catch maps:size(id(<<>>))),
ok.
+t_groups_from_list(_Config) ->
+ #{} = maps:groups_from_list(fun erlang:length/1, []),
+ #{3 := ["tna","tac"], 5 := ["ognid"], 7 := ["olaffub"]} =
+ maps:groups_from_list(
+ fun erlang:length/1,
+ fun lists:reverse/1,
+ ["ant", "buffalo", "cat", "dingo"]
+ ),
+ #{0 := [2], 1 := [1, 3]} = maps:groups_from_list(fun(X) -> X rem 2 end, [1, 2, 3]).
+
error_info(_Config) ->
BadIterator = [-1|#{}],
GoodIterator = maps:iterator(#{}),
@@ -806,6 +818,13 @@ error_info(_Config) ->
{get, [key, {no,map}]},
{get, [key, {no,map}, default]},
+ {groups_from_list, [not_a_fun, []]},
+ {groups_from_list, [fun hd/1, not_a_list]},
+
+ {groups_from_list, [not_a_fun, fun(_) -> ok end, []]},
+ {groups_from_list, [fun(_) -> ok end, not_a_fun, []]},
+ {groups_from_list, [fun(_) -> ok end, fun(_) -> ok end, not_a_list]},
+
{intersect, [#{a => b}, y]},
{intersect, [x, #{a => b}]},
{intersect, [x, y],[{1,".*"},{2,".*"}]},
--
2.34.1