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

openSUSE Build Service is sponsored by