%%% vi:ts=4 sw=4 et %%% Copyright (c) 2008 Robert Virding. All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% 1. Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% 2. Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in the %%% documentation and/or other materials provided with the distribution. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS %%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT %%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS %%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE %%% COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, %%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, %%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; %%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER %%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT %%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN %%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. %%%------------------------------------------------------------------- %%% @copyright 2008 Robert Verding %%% %%% @doc %%% %%% Rbdict implements a Key - Value dictionary. An rbdict is a %%% representation of a dictionary, where a red-black tree is used to %%% store the keys and values. %%% %%% This module implements exactly the same interface as the module %%% ec_dictionary but with a defined representation. One difference is %%% that while dict considers two keys as different if they do not %%% match (=:=), this module considers two keys as different if and %%% only if they do not compare equal (==). %%% %%% The algorithms here are taken directly from Okasaki and Rbset %%% in ML/Scheme. The interface is compatible with the standard dict %%% interface. %%% %%% The following structures are used to build the the RB-dict: %%% %%% {r,Left,Key,Val,Right} %%% {b,Left,Key,Val,Right} %%% empty %%% %%% It is interesting to note that expanding out the first argument of %%% l/rbalance, the colour, in store etc. is actually slower than not %%% doing it. Measured. %%% %%% see ec_dictionary %%% @end %%%------------------------------------------------------------------- -module(ec_rbdict). -behaviour(ec_dictionary). %% Standard interface. -export([add/3, from_list/1, get/2, get/3, has_key/2, has_value/2, new/0, remove/2, size/1, to_list/1, keys/1]). -export_type([dictionary/2]). %%%=================================================================== %%% Types %%%=================================================================== %% This should be opaque, but that kills dialyzer so for now we export it %% however you should not rely on the internal representation here -type dictionary(K, V) :: empty | {color(), dictionary(K, V), ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)}. -type color() :: r | b. %%%=================================================================== %%% API %%%=================================================================== -spec new() -> dictionary(_K, _V). new() -> empty. -spec has_key(ec_dictionary:key(K), dictionary(K, _V)) -> boolean(). has_key(_, empty) -> false; has_key(K, {_, Left, K1, _, _}) when K < K1 -> has_key(K, Left); has_key(K, {_, _, K1, _, Right}) when K > K1 -> has_key(K, Right); has_key(_, {_, _, _, _, _}) -> true. -spec get(ec_dictionary:key(K), dictionary(K, V)) -> ec_dictionary:value(V). get(_, empty) -> throw(not_found); get(K, {_, Left, K1, _, _}) when K < K1 -> get(K, Left); get(K, {_, _, K1, _, Right}) when K > K1 -> get(K, Right); get(_, {_, _, _, Val, _}) -> Val. -spec get(ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> ec_dictionary:value(V). get(_, Default, empty) -> Default; get(K, Default, {_, Left, K1, _, _}) when K < K1 -> get(K, Default, Left); get(K, Default, {_, _, K1, _, Right}) when K > K1 -> get(K, Default, Right); get(_, _, {_, _, _, Val, _}) -> Val. -spec add(ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> dictionary(K, V). add(Key, Value, Dict) -> {_, L, K1, V1, R} = add1(Key, Value, Dict), {b, L, K1, V1, R}. -spec remove(ec_dictionary:key(K), dictionary(K, V)) -> dictionary(K, V). remove(Key, Dictionary) -> {Dict1, _} = erase_aux(Key, Dictionary), Dict1. -spec has_value(ec_dictionary:value(V), dictionary(_K, V)) -> boolean(). has_value(Value, Dict) -> fold(fun (_, NValue, _) when NValue == Value -> true; (_, _, Acc) -> Acc end, false, Dict). -spec size(dictionary(_K, _V)) -> non_neg_integer(). size(T) -> size1(T). -spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K), ec_dictionary:value(V)}]. to_list(T) -> to_list(T, []). -spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) -> dictionary(K, V). from_list(L) -> lists:foldl(fun ({K, V}, D) -> add(K, V, D) end, new(), L). -spec keys(dictionary(K, _V)) -> [ec_dictionary:key(K)]. keys(Dict) -> keys(Dict, []). %%%=================================================================== %%% Enternal functions %%%=================================================================== -spec keys(dictionary(K, _V), [ec_dictionary:key(K)]) -> [ec_dictionary:key(K)]. keys(empty, Tail) -> Tail; keys({_, L, K, _, R}, Tail) -> keys(L, [K | keys(R, Tail)]). -spec erase_aux(ec_dictionary:key(K), dictionary(K, V)) -> {dictionary(K, V), boolean()}. erase_aux(_, empty) -> {empty, false}; erase_aux(K, {b, A, Xk, Xv, B}) -> if K < Xk -> {A1, Dec} = erase_aux(K, A), if Dec -> unbalright(b, A1, Xk, Xv, B); true -> {{b, A1, Xk, Xv, B}, false} end; K > Xk -> {B1, Dec} = erase_aux(K, B), if Dec -> unballeft(b, A, Xk, Xv, B1); true -> {{b, A, Xk, Xv, B1}, false} end; true -> case B of empty -> blackify(A); _ -> {B1, {Mk, Mv}, Dec} = erase_min(B), if Dec -> unballeft(b, A, Mk, Mv, B1); true -> {{b, A, Mk, Mv, B1}, false} end end end; erase_aux(K, {r, A, Xk, Xv, B}) -> if K < Xk -> {A1, Dec} = erase_aux(K, A), if Dec -> unbalright(r, A1, Xk, Xv, B); true -> {{r, A1, Xk, Xv, B}, false} end; K > Xk -> {B1, Dec} = erase_aux(K, B), if Dec -> unballeft(r, A, Xk, Xv, B1); true -> {{r, A, Xk, Xv, B1}, false} end; true -> case B of empty -> {A, false}; _ -> {B1, {Mk, Mv}, Dec} = erase_min(B), if Dec -> unballeft(r, A, Mk, Mv, B1); true -> {{r, A, Mk, Mv, B1}, false} end end end. -spec erase_min(dictionary(K, V)) -> {dictionary(K, V), {ec_dictionary:key(K), ec_dictionary:value(V)}, boolean()}. erase_min({b, empty, Xk, Xv, empty}) -> {empty, {Xk, Xv}, true}; erase_min({b, empty, Xk, Xv, {r, A, Yk, Yv, B}}) -> {{b, A, Yk, Yv, B}, {Xk, Xv}, false}; erase_min({b, empty, _, _, {b, _, _, _, _}}) -> exit(boom); erase_min({r, empty, Xk, Xv, A}) -> {A, {Xk, Xv}, false}; erase_min({b, A, Xk, Xv, B}) -> {A1, Min, Dec} = erase_min(A), if Dec -> {T, Dec1} = unbalright(b, A1, Xk, Xv, B), {T, Min, Dec1}; true -> {{b, A1, Xk, Xv, B}, Min, false} end; erase_min({r, A, Xk, Xv, B}) -> {A1, Min, Dec} = erase_min(A), if Dec -> {T, Dec1} = unbalright(r, A1, Xk, Xv, B), {T, Min, Dec1}; true -> {{r, A1, Xk, Xv, B}, Min, false} end. blackify({r, A, K, V, B}) -> {{b, A, K, V, B}, false}; blackify(Node) -> {Node, true}. unballeft(r, {b, A, Xk, Xv, B}, Yk, Yv, C) -> {lbalance(b, {r, A, Xk, Xv, B}, Yk, Yv, C), false}; unballeft(b, {b, A, Xk, Xv, B}, Yk, Yv, C) -> {lbalance(b, {r, A, Xk, Xv, B}, Yk, Yv, C), true}; unballeft(b, {r, A, Xk, Xv, {b, B, Yk, Yv, C}}, Zk, Zv, D) -> {{b, A, Xk, Xv, lbalance(b, {r, B, Yk, Yv, C}, Zk, Zv, D)}, false}. unbalright(r, A, Xk, Xv, {b, B, Yk, Yv, C}) -> {rbalance(b, A, Xk, Xv, {r, B, Yk, Yv, C}), false}; unbalright(b, A, Xk, Xv, {b, B, Yk, Yv, C}) -> {rbalance(b, A, Xk, Xv, {r, B, Yk, Yv, C}), true}; unbalright(b, A, Xk, Xv, {r, {b, B, Yk, Yv, C}, Zk, Zv, D}) -> {{b, rbalance(b, A, Xk, Xv, {r, B, Yk, Yv, C}), Zk, Zv, D}, false}. -spec fold(fun((ec_dictionary:key(K), ec_dictionary:value(V), any()) -> any()), any(), dictionary(K, V)) -> any(). fold(_, Acc, empty) -> Acc; fold(F, Acc, {_, A, Xk, Xv, B}) -> fold(F, F(Xk, Xv, fold(F, Acc, B)), A). add1(K, V, empty) -> {r, empty, K, V, empty}; add1(K, V, {C, Left, K1, V1, Right}) when K < K1 -> lbalance(C, add1(K, V, Left), K1, V1, Right); add1(K, V, {C, Left, K1, V1, Right}) when K > K1 -> rbalance(C, Left, K1, V1, add1(K, V, Right)); add1(K, V, {C, L, _, _, R}) -> {C, L, K, V, R}. size1(empty) -> 0; size1({_, L, _, _, R}) -> size1(L) + size1(R) + 1. to_list(empty, List) -> List; to_list({_, A, Xk, Xv, B}, List) -> to_list(A, [{Xk, Xv} | to_list(B, List)]). %% Balance a tree after (possibly) adding a node to the left/right. -spec lbalance(color(), dictionary(K, V), ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> dictionary(K, V). lbalance(b, {r, {r, A, Xk, Xv, B}, Yk, Yv, C}, Zk, Zv, D) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; lbalance(b, {r, A, Xk, Xv, {r, B, Yk, Yv, C}}, Zk, Zv, D) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; lbalance(C, A, Xk, Xv, B) -> {C, A, Xk, Xv, B}. -spec rbalance(color(), dictionary(K, V), ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> dictionary(K, V). rbalance(b, A, Xk, Xv, {r, {r, B, Yk, Yv, C}, Zk, Zv, D}) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; rbalance(b, A, Xk, Xv, {r, B, Yk, Yv, {r, C, Zk, Zv, D}}) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; rbalance(C, A, Xk, Xv, B) -> {C, A, Xk, Xv, B}.