add suport and documentation for signatures for dictionaries

Signed-off-by: Jordan Wilberding <jwilberding@gmail.com>
This commit is contained in:
Eric Merritt 2011-10-14 12:29:28 -05:00 committed by Jordan Wilberding
parent cea6de7c84
commit ff3557d883
11 changed files with 2082 additions and 3 deletions

103
src/ec_assoc_list.erl Normal file
View file

@ -0,0 +1,103 @@
%%%-------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright 2011 Erlware, LLC.
%%% @doc
%%% provides an implementation of ec_dictionary using an association
%%% list as a basy
%%% @end
%%% @see ec_dictionary
%%%-------------------------------------------------------------------
-module(ec_assoc_list).
-behaviour(ec_dictionary).
%% API
-export([new/0,
has_key/2,
get/2,
get/3,
add/3,
remove/2,
has_value/2,
size/1,
to_list/1,
from_list/1,
keys/1]).
-export_type([dictionary/2]).
%%%===================================================================
%%% Types
%%%===================================================================
-opaque dictionary(K, V) :: {ec_assoc_list,
[{ec_dictionary:key(K), ec_dictionary:value(V)}]}.
%%%===================================================================
%%% API
%%%===================================================================
-spec new() -> dictionary(_K, _V).
new() ->
{ec_assoc_list, []}.
-spec has_key(ec_dictionary:key(K), Object::dictionary(K, _V)) -> boolean().
has_key(Key, {ec_assoc_list, Data}) ->
lists:keymember(Key, 1, Data).
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, {ec_assoc_list, Data}) ->
case lists:keyfind(Key, 1, Data) of
{Key, Value} ->
Value;
false ->
throw(not_found)
end.
-spec get(ec_dictionary:key(K),
ec_dictionary:value(V),
Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Default, {ec_assoc_list, Data}) ->
case lists:keyfind(Key, 1, Data) of
{Key, Value} ->
Value;
false ->
Default
end.
-spec add(ec_dictionary:key(K), ec_dictionary:value(V),
Object::dictionary(K, V)) ->
dictionary(K, V).
add(Key, Value, {ec_assoc_list, _Data}=Dict) ->
{ec_assoc_list, Rest} = remove(Key,Dict),
{ec_assoc_list, [{Key, Value} | Rest ]}.
-spec remove(ec_dictionary:key(K), Object::dictionary(K, _V)) ->
dictionary(K, _V).
remove(Key, {ec_assoc_list, Data}) ->
{ec_assoc_list, lists:keydelete(Key, 1, Data)}.
-spec has_value(ec_dictionary:value(V), Object::dictionary(_K, V)) -> boolean().
has_value(Value, {ec_assoc_list, Data}) ->
lists:keymember(Value, 2, Data).
-spec size(Object::dictionary(_K, _V)) -> integer().
size({ec_assoc_list, Data}) ->
length(Data).
-spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K),
ec_dictionary:value(V)}].
to_list({ec_assoc_list, Data}) ->
Data.
-spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) ->
dictionary(K, V).
from_list(List) when is_list(List) ->
{ec_assoc_list, List}.
-spec keys(dictionary(K, _V)) -> [ec_dictionary:key(K)].
keys({ec_assoc_list, Data}) ->
lists:map(fun({Key, _Value}) ->
Key
end, Data).

107
src/ec_dict.erl Normal file
View file

@ -0,0 +1,107 @@
%%%-------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright 2011 Erlware, LLC.
%%% @doc
%%% This provides an implementation of the ec_dictionary type using
%%% erlang dicts as a base. The function documentation for
%%% ec_dictionary applies here as well.
%%% @end
%%% @see ec_dictionary
%%% @see dict
%%%-------------------------------------------------------------------
-module(ec_dict).
-behaviour(ec_dictionary).
%% API
-export([new/0,
has_key/2,
get/2,
get/3,
add/3,
remove/2,
has_value/2,
size/1,
to_list/1,
from_list/1,
keys/1]).
-export_type([dictionary/2]).
%%%===================================================================
%%% Types
%%%===================================================================
-opaque dictionary(_K, _V) :: dict().
%%%===================================================================
%%% API
%%%===================================================================
-spec new() -> dictionary(_K, _V).
new() ->
dict:new().
-spec has_key(ec_dictionary:key(K), Object::dictionary(K, _V)) -> boolean().
has_key(Key, Data) ->
dict:is_key(Key, Data).
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Data) ->
case dict:find(Key, Data) of
{ok, Value} ->
Value;
error ->
throw(not_found)
end.
-spec get(ec_dictionary:key(K),
ec_dictionary:value(V),
Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Default, Data) ->
case dict:find(Key, Data) of
{ok, Value} ->
Value;
error ->
Default
end.
-spec add(ec_dictionary:key(K), ec_dictionary:value(V),
Object::dictionary(K, V)) ->
dictionary(K, V).
add(Key, Value, Data) ->
dict:store(Key, Value, Data).
-spec remove(ec_dictionary:key(K), Object::dictionary(K, V)) ->
dictionary(K, V).
remove(Key, Data) ->
dict:erase(Key, Data).
-spec has_value(ec_dictionary:value(V), Object::dictionary(_K, V)) -> boolean().
has_value(Value, Data) ->
dict:fold(fun(_, NValue, _) when NValue == Value ->
true;
(_, _, Acc) ->
Acc
end,
false,
Data).
-spec size(Object::dictionary(_K, _V)) -> integer().
size(Data) ->
dict:size(Data).
-spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K),
ec_dictionary:value(V)}].
to_list(Data) ->
dict:to_list(Data).
-spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) ->
dictionary(K, V).
from_list(List) when is_list(List) ->
dict:from_list(List).
-spec keys(dictionary(K, _V)) -> [ec_dictionary:key(K)].
keys(Dict) ->
dict:fetch_keys(Dict).

158
src/ec_dictionary.erl Normal file
View file

@ -0,0 +1,158 @@
%%%-------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright 2011 Erlware, LLC.
%%% @doc
%%% A module that supports association of keys to values. A map cannot
%%% contain duplicate keys; each key can map to at most one value.
%%%
%%% This interface is a member of the Erlware Commons Library.
%%% @end
%%%-------------------------------------------------------------------
-module(ec_dictionary).
%%% Behaviour Callbacks
-export([behaviour_info/1]).
%% API
-export([new/1,
has_key/2,
get/2,
get/3,
add/3,
remove/2,
has_value/2,
size/1,
to_list/1,
from_list/2,
keys/1]).
-export_type([dictionary/2,
key/1,
value/1]).
%%%===================================================================
%%% Types
%%%===================================================================
-record(dict_t,
{callback,
data}).
-opaque dictionary(_K, _V) :: #dict_t{}.
-type key(T) :: T.
-type value(T) :: T.
%%%===================================================================
%%% API
%%%===================================================================
%% @doc export the behaviour callbacks for this type
%% @private
behaviour_info(callbacks) ->
[{new, 0},
{has_key, 2},
{get, 2},
{add, 3},
{remove, 2},
{has_value, 2},
{size, 1},
{to_list, 1},
{from_list, 1},
{keys, 1}];
behaviour_info(_) ->
undefined.
%% @doc create a new dictionary object from the specified module. The
%% module should implement the dictionary behaviour.
%%
%% @param ModuleName The module name.
-spec new(module()) -> dictionary(_K, _V).
new(ModuleName) when is_atom(ModuleName) ->
#dict_t{callback = ModuleName, data = ModuleName:new()}.
%% @doc check to see if the dictionary provided has the specified key.
%%
%% @param Dict The dictory object to check
%% @param Key The key to check the dictionary for
-spec has_key(key(K), dictionary(K, _V)) -> boolean().
has_key(Key, #dict_t{callback = Mod, data = Data}) ->
Mod:has_key(Key, Data).
%% @doc given a key return that key from the dictionary. If the key is
%% not found throw a 'not_found' exception.
%%
%% @param Dict The dictionary object to return the value from
%% @param Key The key requested
%% @throws not_found when the key does not exist
-spec get(key(K), dictionary(K, V)) -> value(V).
get(Key, #dict_t{callback = Mod, data = Data}) ->
Mod:get(Key, Data).
%% @doc given a key return that key from the dictionary. If the key is
%% not found then the default value is returned.
%%
%% @param Dict The dictionary object to return the value from
%% @param Key The key requested
%% @param Default The value that will be returned if no value is found
%% in the database.
-spec get(key(K), value(V), dictionary(K, V)) -> value(V).
get(Key, Default, #dict_t{callback = Mod, data = Data}) ->
Mod:get(Key, Default, Data).
%% @doc add a new value to the existing dictionary. Return a new
%% dictionary containing the value.
%%
%% @param Dict the dictionary object to add too
%% @param Key the key to add
%% @param Value the value to add
-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V).
add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) ->
Dict#dict_t{data = Mod:add(Key, Value, Data)}.
%% @doc Remove a value from the dictionary returning a new dictionary
%% with the value removed.
%%
%% @param Dict the dictionary object to remove the value from
%% @param Key the key of the key/value pair to remove
-spec remove(key(K), dictionary(K, V)) -> dictionary(K, V).
remove(Key, #dict_t{callback = Mod, data = Data} = Dict) ->
Dict#dict_t{data = Mod:remove(Key, Data)}.
%% @doc Check to see if the value exists in the dictionary
%%
%% @param Dict the dictionary object to check
%% @param Value The value to check if exists
-spec has_value(value(V), dictionary(_K, V)) -> boolean().
has_value(Value, #dict_t{callback = Mod, data = Data}) ->
Mod:has_value(Value, Data).
%% @doc return the current number of key value pairs in the dictionary
%%
%% @param Dict the object return the size for.
-spec size(dictionary(_K, _V)) -> integer().
size(#dict_t{callback = Mod, data = Data}) ->
Mod:size(Data).
%% @doc Return the contents of this dictionary as a list of key value
%% pairs.
%%
%% @param Dict the base dictionary to make use of.
-spec to_list(Dict::dictionary(K, V)) -> [{key(K), value(V)}].
to_list(#dict_t{callback = Mod, data = Data}) ->
Mod:to_list(Data).
%% @doc Create a new dictionary, of the specified implementation using
%% the list provided as the starting contents.
%%
%% @param ModuleName the type to create the dictionary from
%% @param List The list of key value pairs to start with
-spec from_list(module(), [{key(K), value(V)}]) -> dictionary(K, V).
from_list(ModuleName, List) when is_list(List) ->
#dict_t{callback = ModuleName, data = ModuleName:from_list(List)}.
%% @doc Return the keys of this dictionary as a list
%%
%% @param Dict the base dictionary to make use of.
-spec keys(Dict::dictionary(K, _V)) -> [key(K)].
keys(#dict_t{callback = Mod, data = Data}) ->
Mod:keys(Data).

223
src/ec_gb_trees.erl Normal file
View file

@ -0,0 +1,223 @@
%%%-------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright 2011 Erlware, LLC.
%%% @doc
%%% This provides an implementation of the type ec_dictionary using
%%% gb_trees as a backin
%%% @end
%%% @see ec_dictionary
%%% @see gb_trees
%%%-------------------------------------------------------------------
-module(ec_gb_trees).
-behaviour(ec_dictionary).
%% API
-export([new/0,
has_key/2,
get/2,
get/3,
add/3,
remove/2,
has_value/2,
size/1,
to_list/1,
from_list/1,
keys/1]).
-export_type([dictionary/2]).
%%%===================================================================
%%% Types
%%%===================================================================
-opaque dictionary(K, V) :: {non_neg_integer(), ec_gb_tree_node(K, V)}.
-type ec_gb_tree_node(K, V) :: 'nil' | {K, V,
ec_gb_tree_node(K, V),
ec_gb_tree_node(K, V)}.
%%%===================================================================
%%% API
%%%===================================================================
%% @doc create a new dictionary object from the specified module. The
%% module should implement the dictionary behaviour. In the clause
%% where an existing object is passed in new empty dictionary of the
%% same implementation is created and returned.
%%
%% @param ModuleName|Object The module name or existing dictionary object.
-spec new() -> dictionary(_K, _V).
new() ->
gb_trees:empty().
%% @doc check to see if the dictionary provided has the specified key.
%%
%% @param Object The dictory object to check
%% @param Key The key to check the dictionary for
-spec has_key(ec_dictionary:key(K), Object::dictionary(K, _V)) -> boolean().
has_key(Key, Data) ->
case gb_trees:lookup(Key, Data) of
{value, _Val} ->
true;
none ->
false
end.
%% @doc given a key return that key from the dictionary. If the key is
%% not found throw a 'not_found' exception.
%%
%% @param Object The dictionary object to return the value from
%% @param Key The key requested
%% @throws not_found when the key does not exist
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Data) ->
case gb_trees:lookup(Key, Data) of
{value, Value} ->
Value;
none ->
throw(not_found)
end.
-spec get(ec_dictionary:key(K),
ec_dictionary:value(V),
Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Default, Data) ->
case gb_trees:lookup(Key, Data) of
{value, Value} ->
Value;
none ->
Default
end.
%% @doc add a new value to the existing dictionary. Return a new
%% dictionary containing the value.
%%
%% @param Object the dictionary object to add too
%% @param Key the key to add
%% @param Value the value to add
-spec add(ec_dictionary:key(K), ec_dictionary:value(V),
Object::dictionary(K, V)) ->
dictionary(K, V).
add(Key, Value, Data) ->
gb_trees:enter(Key, Value, Data).
%% @doc Remove a value from the dictionary returning a new dictionary
%% with the value removed.
%%
%% @param Object the dictionary object to remove the value from
%% @param Key the key of the key/value pair to remove
-spec remove(ec_dictionary:key(K), Object::dictionary(K, V)) ->
dictionary(K, V).
remove(Key, Data) ->
gb_trees:delete_any(Key, Data).
%% @doc Check to see if the value exists in the dictionary
%%
%% @param Object the dictionary object to check
%% @param Value The value to check if exists
-spec has_value(ec_dictionary:value(V), Object::dictionary(_K, V)) -> boolean().
has_value(Value, Data) ->
lists:member(Value, gb_trees:values(Data)).
%% @doc return the current number of key value pairs in the dictionary
%%
%% @param Object the object return the size for.
-spec size(Object::dictionary(_K, _V)) -> integer().
size(Data) ->
gb_trees:size(Data).
-spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K),
ec_dictionary:value(V)}].
to_list(Data) ->
gb_trees:to_list(Data).
-spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) ->
dictionary(K, V).
from_list(List) when is_list(List) ->
lists:foldl(fun({Key, Value}, Dict) ->
gb_trees:enter(Key, Value, Dict)
end,
gb_trees:empty(),
List).
-spec keys(dictionary(K,_V)) -> [ec_dictionary:key(K)].
keys(Data) ->
gb_trees:keys(Data).
%%%===================================================================
%%% Tests
%%%===================================================================
-ifndef(NOTEST).
-include_lib("eunit/include/eunit.hrl").
%% For me unit testing initially is about covering the obvious case. A
%% check to make sure that what you expect the tested functionality to
%% do, it actually does. As time goes on and people detect bugs you
%% add tests for those specific problems to the unit test suit.
%%
%% However, when getting started you can only test your basic
%% expectations. So here are the expectations I have for the add
%% functionality.
%%
%% 1) I can put arbitrary terms into the dictionary as keys
%% 2) I can put arbitrary terms into the dictionary as values
%% 3) When I put a value in the dictionary by a key, I can retrieve
%% that same value
%% 4) When I put a different value in the dictionary by key it does
%% not change other key value pairs.
%% 5) When I update a value the new value in available by the new key
%% 6) When a value does not exist a not found exception is created
add_test() ->
Dict0 = ec_dictionary:new(ec_gb_trees),
Key1 = foo,
Key2 = [1, 3],
Key3 = {"super"},
Key4 = <<"fabulous">>,
Key5 = {"Sona", 2, <<"Zuper">>},
Value1 = Key5,
Value2 = Key4,
Value3 = Key2,
Value4 = Key3,
Value5 = Key1,
Dict01 = ec_dictionary:add(Key1, Value1, Dict0),
Dict02 = ec_dictionary:add(Key3, Value3,
ec_dictionary:add(Key2, Value2,
Dict01)),
Dict1 =
ec_dictionary:add(Key5, Value5,
ec_dictionary:add(Key4, Value4,
Dict02)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict1)),
?assertMatch(Value2, ec_dictionary:get(Key2, Dict1)),
?assertMatch(Value3, ec_dictionary:get(Key3, Dict1)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict1)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict1)),
Dict2 = ec_dictionary:add(Key3, Value5,
ec_dictionary:add(Key2, Value4, Dict1)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key2, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key3, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict2)),
?assertThrow(not_found, ec_dictionary:get(should_blow_up, Dict2)),
?assertThrow(not_found, ec_dictionary:get("This should blow up too",
Dict2)).
-endif.

107
src/ec_orddict.erl Normal file
View file

@ -0,0 +1,107 @@
%%%-------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright 2011 Erlware, LLC.
%%% @doc
%%% This provides an implementation of the ec_dictionary type using
%%% erlang orddicts as a base. The function documentation for
%%% ec_dictionary applies here as well.
%%% @end
%%% @see ec_dictionary
%%% @see orddict
%%%-------------------------------------------------------------------
-module(ec_orddict).
-behaviour(ec_dictionary).
%% API
-export([new/0,
has_key/2,
get/2,
get/3,
add/3,
remove/2,
has_value/2,
size/1,
to_list/1,
from_list/1,
keys/1]).
-export_type([dictionary/2]).
%%%===================================================================
%%% Types
%%%===================================================================
-opaque dictionary(K, V) :: [{K, V}].
%%%===================================================================
%%% API
%%%===================================================================
-spec new() -> dictionary(_K, _V).
new() ->
orddict:new().
-spec has_key(ec_dictionary:key(K), Object::dictionary(K, _V)) -> boolean().
has_key(Key, Data) ->
orddict:is_key(Key, Data).
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Data) ->
case orddict:find(Key, Data) of
{ok, Value} ->
Value;
error ->
throw(not_found)
end.
-spec get(ec_dictionary:key(K),
Default::ec_dictionary:value(V),
Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Default, Data) ->
case orddict:find(Key, Data) of
{ok, Value} ->
Value;
error ->
Default
end.
-spec add(ec_dictionary:key(K), ec_dictionary:value(V),
Object::dictionary(K, V)) ->
dictionary(K, V).
add(Key, Value, Data) ->
orddict:store(Key, Value, Data).
-spec remove(ec_dictionary:key(K), Object::dictionary(K, V)) ->
dictionary(K, V).
remove(Key, Data) ->
orddict:erase(Key, Data).
-spec has_value(ec_dictionary:value(V), Object::dictionary(_K, V)) -> boolean().
has_value(Value, Data) ->
orddict:fold(fun(_, NValue, _) when NValue == Value ->
true;
(_, _, Acc) ->
Acc
end,
false,
Data).
-spec size(Object::dictionary(_K, _V)) -> integer().
size(Data) ->
orddict:size(Data).
-spec to_list(dictionary(K, V)) ->
[{ec_dictionary:key(K), ec_dictionary:value(V)}].
to_list(Data) ->
orddict:to_list(Data).
-spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) ->
dictionary(K, V).
from_list(List) when is_list(List) ->
orddict:from_list(List).
-spec keys(dictionary(K, _V)) -> [ec_dictionary:key(K)].
keys(Dict) ->
orddict:fetch_keys(Dict).

319
src/ec_rbdict.erl Normal file
View file

@ -0,0 +1,319 @@
%%% 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 implents 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.
%%%
%%% @end
%%% @see ec_dictionary
%%%-------------------------------------------------------------------
-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
%%%===================================================================
-opaque 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_dicitonary: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)) -> 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(), dictionary(K, V), dictionary(K, V)) -> dictionary(K, V).
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 afer (possibly) adding a node to the left/right.
-spec lbalance(color(), dictionary(K, V),
ec_dictinary: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_dictinary: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}.