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
|
%% 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).
|
||||||
|
@ -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.
|
||||||
|
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 => '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
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