add suport and documentation for signatures for dictionaries
Signed-off-by: Jordan Wilberding <jwilberding@gmail.com>
This commit is contained in:
parent
cea6de7c84
commit
ff3557d883
11 changed files with 2082 additions and 3 deletions
103
src/ec_assoc_list.erl
Normal file
103
src/ec_assoc_list.erl
Normal 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
107
src/ec_dict.erl
Normal 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
158
src/ec_dictionary.erl
Normal 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
223
src/ec_gb_trees.erl
Normal 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
107
src/ec_orddict.erl
Normal 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
319
src/ec_rbdict.erl
Normal 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}.
|
Loading…
Add table
Add a link
Reference in a new issue