Packages

Code instrumentation and metrics collection package.

Current section

Files

Jump to
basho_exometer_core src exometer_shallowtree.erl
Raw

src/exometer_shallowtree.erl

%% @doc Size-constrained leftist tree
%% Inspired by <a href="http://www.cise.ufl.edu/~sahni/cop5536/powerpoint/lec11.ppt">Leftist Trees</a> 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.