feat: Add bunch of useful functions for seqs (#32)

* feat: Add bunch of useful functions for seqs

* docs: Add docs to new functions

* Update src/genlib_map.erl

Co-authored-by: ndiezel0 <ndiezel0@gmail.com>

* test: Split test for list:wrap into eunit and prop

* test: Split group_by prop tests by semantics

* test: move 0-size test for map:fold_while to unit

* test: simplify prop_fold_while generator

* test: Move white-box unit tests to test/

Co-authored-by: ndiezel0 <ndiezel0@gmail.com>
This commit is contained in:
Yaroslav Rogov 2021-05-25 18:25:18 +03:00 committed by GitHub
parent a4b45b3a89
commit 3e17765368
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 201 additions and 0 deletions

View File

@ -3,6 +3,10 @@
%% API %% API
-export([join/2]). -export([join/2]).
-export([compact/1]). -export([compact/1]).
-export([wrap/1]).
-export([group_by/2]).
-export([foldl_while/3]).
-export([orderless_equal/2]).
%% %%
%% API %% API
@ -21,3 +25,50 @@ compact(List) ->
end, end,
List List
). ).
%% @doc Wrap given term into a list.
%% If a list is given, this list is returned.
%% If undefined is passed, empty list is returned
-spec wrap(undefined | list(T) | T) -> [] | list(T) when T :: term().
wrap(undefined) ->
[];
wrap(List) when is_list(List) ->
List;
wrap(Term) ->
[Term].
%% @doc Group values in a given list by result of KeyFun.
%% The result is a map, where keys are results of KeyFun, and values are list of values of original list
%% that result in such key.
-spec group_by(fun((T) -> K), list(T)) -> #{K := list(T)} when T :: term(), K :: term().
group_by(KeyFun, List) ->
lists:foldl(
fun(Elt, Acc) ->
Key = KeyFun(Elt),
GroupList = maps:get(Key, Acc, []),
maps:put(Key, [Elt | GroupList], Acc)
end,
#{},
List
).
%% @doc Like lists:foldl, but can be stopped amid the traversal of a list.
%% Function must return {cont, NewAcc} to continue folding the list, or {halt, FinalAcc} to stop immediately.
-spec foldl_while(fun((T, Acc) -> {cont, Acc} | {halt, Acc}), Acc, list(T)) -> Acc when T :: term(), Acc :: term().
foldl_while(Fun, Acc, List) when is_function(Fun, 2), is_list(List) ->
do_foldl_while(Fun, Acc, List).
do_foldl_while(_Fun, Acc, []) ->
Acc;
do_foldl_while(Fun, Acc, [Elem | Rest]) ->
case Fun(Elem, Acc) of
{cont, NextAcc} ->
do_foldl_while(Fun, NextAcc, Rest);
{halt, FinalAcc} ->
FinalAcc
end.
-spec orderless_equal(list(), list()) -> boolean().
orderless_equal(List1, List2) ->
(List1 -- List2) =:= (List2 -- List1).

View File

@ -17,6 +17,7 @@
-export([binarize/1]). -export([binarize/1]).
-export([binarize/2]). -export([binarize/2]).
-export([diff/2]). -export([diff/2]).
-export([fold_while/3]).
%% %%
@ -142,3 +143,21 @@ diff(Map, Since) ->
Map, Map,
Since Since
). ).
%% @doc Like maps:fold, but can be stopped amid the traversal of a map.
%% Function must return {cont, NewAcc} to continue folding the list, or {halt, FinalAcc} to stop immediately.
-spec fold_while(fun((K, V, Acc) -> {cont, Acc} | {halt, Acc}), Acc, #{K => V}) -> Acc when
K :: term(), V :: term(), Acc :: term().
fold_while(Fun, Acc, Map) when is_function(Fun, 3) and is_map(Map) ->
do_fold_while(Fun, Acc, maps:iterator(Map)).
do_fold_while(Fun, Acc, Iter) ->
case maps:next(Iter) of
none ->
Acc;
{K, V, NextIter} ->
case Fun(K, V, Acc) of
{halt, FinalAcc} -> FinalAcc;
{cont, NextAcc} -> do_fold_while(Fun, NextAcc, NextIter)
end
end.

View File

@ -0,0 +1,10 @@
-module(genlib_list_tests).
-include_lib("eunit/include/eunit.hrl").
-spec test() -> _.
-spec wrap_empty_list_for_undefined_test() -> _.
wrap_empty_list_for_undefined_test() ->
?assertEqual(genlib_list:wrap(undefined), []).

View File

@ -48,3 +48,13 @@ diff_test_() ->
?_assertEqual(#{this_is => 'undefined'}, genlib_map:diff(#{this_is => 'undefined'}, #{this_is => 'not'})), ?_assertEqual(#{this_is => 'undefined'}, genlib_map:diff(#{this_is => 'undefined'}, #{this_is => 'not'})),
?_assertEqual(#{this_is => 'not'}, genlib_map:diff(#{this_is => 'not'}, #{this_is => 'undefined'})) ?_assertEqual(#{this_is => 'not'}, genlib_map:diff(#{this_is => 'not'}, #{this_is => 'undefined'}))
]. ].
-spec fold_while_does_nothing_for_empty_map_test() -> _.
fold_while_does_nothing_for_empty_map_test() ->
genlib_map:fold_while(
fun(_, _, _) ->
throw(blow_up)
end,
true,
#{}
).

85
test/prop_genlib_list.erl Normal file
View File

@ -0,0 +1,85 @@
-module(prop_genlib_list).
-include_lib("proper/include/proper.hrl").
-spec prop_wrap() -> proper:test().
prop_wrap() ->
?FORALL(
Term,
term(),
begin
Result = genlib_list:wrap(Term),
%% List must stay the same, anything else (except `undefined`) ­
(is_list(Term) and (Result == Term)) or is_list(Result)
end
).
-spec prop_foldl_while() -> proper:test().
prop_foldl_while() ->
?FORALL(
[Int1, Int2],
[pos_integer(), pos_integer()],
begin
SeqLength = max(Int1, Int2),
Limit = min(Int1, Int2),
Seq = lists:seq(1, SeqLength),
SimpleResult = length(lists:filter(fun(E) -> E =< Limit end, Seq)),
FoldlWhileResult =
genlib_list:foldl_while(
fun(E, Acc) ->
NewAcc = Acc + 1,
case E < Limit of
true -> {cont, NewAcc};
false -> {halt, NewAcc}
end
end,
0,
Seq
),
SimpleResult =:= FoldlWhileResult
end
).
-spec prop_orderless_equal() -> proper:test().
prop_orderless_equal() ->
?FORALL(
[List1, List2],
[list(), list()],
genlib_list:orderless_equal(List1, List2) == (lists:sort(List1) == lists:sort(List2))
).
-spec prop_group_by_has_same_values() -> proper:test().
prop_group_by_has_same_values() ->
?FORALL(
List,
list(),
begin
Result = genlib_list:group_by(fun(X) -> X end, List),
AllValues = lists:flatmap(fun(X) -> X end, maps:values(Result)),
lists:sort(List) =:= lists:sort(AllValues)
end
).
-spec prop_group_by_groups_correctly() -> proper:test().
prop_group_by_groups_correctly() ->
?FORALL(
List,
list(),
begin
Result = genlib_list:group_by(fun(X) -> X end, List),
maps:fold(
fun
(_, _, false) ->
false;
(Key, Values, true) ->
Filtered = lists:filter(fun(K) -> K =:= Key end, List),
genlib_list:orderless_equal(Values, Filtered)
end,
true,
Result
)
end
).

26
test/prop_genlib_map.erl Normal file
View File

@ -0,0 +1,26 @@
-module(prop_genlib_map).
-include_lib("proper/include/proper.hrl").
-spec prop_fold_while() -> proper:test().
prop_fold_while() ->
?FORALL(
Map,
?LET(KVList, non_empty(list({term(), term()})), maps:from_list(KVList)),
begin
RandomId = rand:uniform(map_size(Map)),
{RandomKey, RandomValue} = lists:nth(RandomId, maps:to_list(Map)),
Result =
genlib_map:fold_while(
fun
(K, V, _Acc) when K =:= RandomKey -> {halt, V};
(_K, _V, Acc) -> {cont, Acc}
end,
make_ref(),
Map
),
Result =:= RandomValue
end
).