%% @doc Size-constrained leftist tree %% Inspired by Leftist Trees by Sartaj Sahni. %% %% The purpose of this module is to efficiently store a limited number of %% values in e.g. a lossy histogram (ex. {@link exometer_slot_slide}). The %% complexity of insert operations is log(N), but once the tree is full, %% only values higher than the minimum value already in the tree will be %% inserted, and the old minimum is deleted - i.e. two O(log N) operations. %% For other values, the cost will be only two comparisons, since the %% top node in the tree always contains the minimum. %% @end -module(exometer_shallowtree). -export([new/1, insert/3, take_min/1, to_list/1, filter/2, size/1, limit/1]). -export([fill/1, fill1/2]). -export_type([tree/0]). -record(t, {size = 0, limit = 10, tree = []}). -type tree() :: #t{}. -spec new(pos_integer()) -> tree(). %% @doc Create an empty tree limited to `Size'. new(Size) when is_integer(Size), Size > 0 -> #t{limit = Size}. -spec size(tree()) -> non_neg_integer(). %% @doc Returns the number of values stored in the given tree. size(#t{size = Sz}) -> Sz. -spec limit(tree()) -> non_neg_integer(). %% @doc Returns the maximum number of values for the given tree. limit(#t{limit = L}) -> L. -spec insert(number(), any(), tree()) -> tree(). %% @doc Insert value `V' into tree `T'. %% %% If the tree is full and `V' is smaller than the minimum, this function %% will return immediately, leaving the tree unchanged. %% @end insert(K, V, #t{size = X, limit = X, tree = Tr} = T) when is_number(K) -> case K =< element(1, Tr) of true -> T; false -> {_, _, Tr1} = take_min_(Tr), T#t{tree = insert_(K, V, Tr1)} end; insert(K, V, #t{size = Sz, tree = Tr} = T) when is_number(K) -> T#t{size = Sz+1, tree = insert_(K, V, Tr)}. insert_(K, V, []) -> mknode(K, V); insert_(K, V, T ) -> meld(mknode(K, V), T). -spec take_min(tree()) -> {number(), any(), tree()} | error. %% @doc Extract the smallest value from the tree `T'. %% %% If the tree is empty, `error' is returned, otherwise `{Minimum, NewTree}'. %% @end take_min(#t{size = Sz, tree = Tr} = T) -> case take_min_(Tr) of error -> error; {K, V, Tr1} -> {K, V, T#t{size = Sz-1, tree = Tr1}} end. take_min_([]) -> error; take_min_({K,V,_,L,R}) -> {K, V, meld(L, R)}. -spec to_list(tree()) -> [{number(), any()}]. %% @doc Converts a tree to a list. %% %% The list will not be ordered, since the aim is to produce the list as %% quickly as possible. Also, `lists:sort(to_list(Tree))', if to_list/1 %% uses brute force, seems faster than most approaches for extracting %% values in order. %% @end to_list(#t{tree = T}) -> to_list_([T]). to_list_([]) -> []; to_list_([{K,V,_,L,R}|T]) -> [{K,V}|to_list_([L,R|T])]; to_list_([[]|T]) -> to_list_(T). filter(F, #t{tree = T}) -> filter_(F, [T]). filter_(_, []) -> []; filter_(F, [{K,V,_,L,R}|T]) -> case F(K,V) of false -> filter_(F, [L,R|T]); {true, Keep} -> [Keep|filter_(F, [L,R|T])] end; filter_(F, [[]|T]) -> filter_(F, T). meld({K1,V1, _, L1, R1} = T1, {K2,V2, _, L2, R2} = T2) -> case K1 < K2 of true -> mknode(K1,V1, L1, meld(R1, T2)); false -> mknode(K2,V2, L2, meld(R2, T1)) end; meld([], T2) -> T2; meld(T1, []) -> T1; meld([], []) -> []. mknode(K,V) -> {K,V,1,[],[]}. mknode(K,V,{_,_,S1,_,_} = T1, {_,_,S2,_,_} = T2) when S1 < S2 -> {K,V, S1+1, T2, T1}; mknode(K,V, [], [] ) -> {K,V, 1 , [], []}; mknode(K,V, [], {_,_,S2,_,_} = T2) -> {K,V, S2+1, T2, []}; mknode(K,V, {_,_,S1,_,_} = T1, []) -> {K,V, S1+1, T1, []}; mknode(K,V, T1, {_,_,S2,_,_} = T2) -> {K,V, S2+1, T1, T2}. fill(Size) -> L = lists:seq(1,Size), T0 = new(Size), timer:tc(?MODULE, fill1, [L, T0]). fill1([H|T], Tree) -> fill1(T, insert(H, x, Tree)); fill1([], Tree) -> Tree.