mirror of
https://github.com/valitydev/genlib.git
synced 2024-11-06 09:15:23 +00:00
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:
parent
a4b45b3a89
commit
3e17765368
@ -3,6 +3,10 @@
|
||||
%% API
|
||||
-export([join/2]).
|
||||
-export([compact/1]).
|
||||
-export([wrap/1]).
|
||||
-export([group_by/2]).
|
||||
-export([foldl_while/3]).
|
||||
-export([orderless_equal/2]).
|
||||
|
||||
%%
|
||||
%% API
|
||||
@ -21,3 +25,50 @@ compact(List) ->
|
||||
end,
|
||||
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).
|
||||
|
@ -17,6 +17,7 @@
|
||||
-export([binarize/1]).
|
||||
-export([binarize/2]).
|
||||
-export([diff/2]).
|
||||
-export([fold_while/3]).
|
||||
|
||||
%%
|
||||
|
||||
@ -142,3 +143,21 @@ diff(Map, Since) ->
|
||||
Map,
|
||||
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.
|
||||
|
10
test/genlib_list_tests.erl
Normal file
10
test/genlib_list_tests.erl
Normal 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), []).
|
@ -48,3 +48,13 @@ diff_test_() ->
|
||||
?_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'}))
|
||||
].
|
||||
|
||||
-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
85
test/prop_genlib_list.erl
Normal 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
26
test/prop_genlib_map.erl
Normal 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
|
||||
).
|
Loading…
Reference in New Issue
Block a user