Changed to any()/any() for key/value pairs and added support for
hash functions larger than 32bits. Other cleanup and fixes here and there.
This commit is contained in:
parent
9a9bebce57
commit
61677cd1b8
1 changed files with 184 additions and 191 deletions
375
src/hamt.erl
375
src/hamt.erl
|
@ -44,7 +44,11 @@
|
||||||
%% -------------------------------------------------------------------------
|
%% -------------------------------------------------------------------------
|
||||||
%% Operations:
|
%% Operations:
|
||||||
%%
|
%%
|
||||||
%% - new(): returns empty hamt.
|
%% - new(): returns empty hamt that uses a 32bit hash function to map
|
||||||
|
%% keys into the trie.
|
||||||
|
%%
|
||||||
|
%% - new(32,64,128,160): returns empty hamt that uses the specified
|
||||||
|
%% size hash function to map keys into the trie.
|
||||||
%%
|
%%
|
||||||
%% - is_empty(T): returns 'true' if T is an empty hamt, and 'false'
|
%% - is_empty(T): returns 'true' if T is an empty hamt, and 'false'
|
||||||
%% otherwise.
|
%% otherwise.
|
||||||
|
@ -82,80 +86,97 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-export([new/0, is_empty/1, get/2, put/2, put/3, delete/2,
|
-export([new/0, new/1,
|
||||||
|
is_empty/1, get/2, put/2, put/3, delete/2,
|
||||||
map/2, fold/3,
|
map/2, fold/3,
|
||||||
from_list/1, to_list/1]).
|
from_list/1, from_list/2, to_list/1]).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
%% The Hamt data structure consists of:
|
%% The Hamt data structure consists of:
|
||||||
%% - {hamt, nil | {SNode, CNode, LNode}
|
%% - {hamt, nil | {SNode, CNode, LNode}
|
||||||
|
%% - {hamt, nil | {hamt, {SNode, CNode, LNode}}
|
||||||
%% - {snode, Key::binary(), Value::binary()}
|
%% - {snode, Key::binary(), Value::binary()}
|
||||||
%% - {cnode, Bitmap, Branch}
|
%% - {cnode, Bitmap, Branch}
|
||||||
%% - {lnode, [snode]}
|
%% - {lnode, [snode]}
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
-export_type([hamt/0, hamt_hash_fn/0]).
|
||||||
%% Some types.
|
|
||||||
|
|
||||||
-export_type([hamt/0]).
|
-type hamt_hash_fn() :: {non_neg_integer(), fun((any()) -> binary())}.
|
||||||
|
-type hamt_snode() :: {snode, any(), any()}.
|
||||||
-type hamt_snode() :: {snode, binary(), binary()}.
|
-type hamt_lnode() :: {lnode, [hamt_snode()]}.
|
||||||
-type hamt_lnode() :: {lnode, [hamt_snode()]}.
|
-type hamt_cnode() :: {cnode, non_neg_integer(),
|
||||||
-type hamt_cnode() :: {cnode, non_neg_integer(), [hamt_snode() | hamt_cnode() | hamt_lnode()]}.
|
[hamt_snode() | hamt_cnode() | hamt_lnode()]}.
|
||||||
-opaque hamt() :: {hamt, non_neg_integer(), nil | hamt_cnode()}.
|
-opaque hamt() :: {hamt, nil | hamt_hash_fn(), nil | hamt_cnode()}.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
|
%% @doc Returns a new, empty trie that uses phash2 to generate
|
||||||
|
%% 32bit hash codes for keys.
|
||||||
-spec new() -> hamt().
|
-spec new() -> hamt().
|
||||||
|
new() -> {hamt, 32, nil}.
|
||||||
|
|
||||||
new() ->
|
%% @doc Returns a new, empty trie that uses the specified
|
||||||
{hamt, nil}.
|
%% number of bits when hashing keys.
|
||||||
|
-spec new(32|64|128|160) -> hamt().
|
||||||
|
new(HashSize) -> {hamt, HashSize, nil}.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
-spec hash(32|64|128|160, any()) -> non_neg_integer().
|
||||||
|
hash(HashSize, X)
|
||||||
|
when not is_binary(X) ->
|
||||||
|
hash(HashSize, term_to_binary(X));
|
||||||
|
hash(32, X) -> murmur3:hash(32, X);
|
||||||
|
%hash(32, X) -> erlang:phash2(X);
|
||||||
|
hash(64, X) -> <<I:64/integer, _/binary>> = crypto:sha(X), I;
|
||||||
|
hash(128, X) -> <<I:128/integer, _/binary>> = crypto:sha(X), I;
|
||||||
|
hash(160, X) -> <<I:160/integer>> = crypto:sha(X), I.
|
||||||
|
|
||||||
-spec is_empty(Hamt) -> boolean() when
|
%% @doc Returns true when the trie is empty.
|
||||||
Hamt :: hamt().
|
-spec is_empty(Hamt::hamt()) -> boolean().
|
||||||
|
is_empty({hamt, _, nil}) ->
|
||||||
is_empty({hamt, nil}) ->
|
|
||||||
true;
|
true;
|
||||||
is_empty(_) ->
|
is_empty({hamt, _, _}) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%% @doc This function searches for a key in a trie. Returns Value where
|
||||||
|
%% Value is the value associated with Key, or error if the key is not
|
||||||
-spec get(Key, Hamt) -> not_found | Value when
|
%% present.
|
||||||
Key :: binary(),
|
-spec get(Key::any(), Hamt::hamt()) -> {error, not_found} | any().
|
||||||
Value :: binary(),
|
get(_Key, {hamt, _, nil}) ->
|
||||||
Hamt :: hamt().
|
{error, not_found};
|
||||||
|
get(Key, {hamt, _, {snode, Key, Value}}) ->
|
||||||
get(_Key, {hamt, nil}) ->
|
|
||||||
not_found;
|
|
||||||
get(Key, {hamt, {snode, Key, Value}}) ->
|
|
||||||
Value;
|
Value;
|
||||||
get(Key, {hamt, {cnode, _Bitmap, _Nodes}=CN}) ->
|
get(_Key, {hamt, _, {snode, _, _}}) ->
|
||||||
case get_1(hash(Key), CN, 0) of
|
{error, not_found};
|
||||||
|
get(Key, {hamt, HashSize, {cnode, _Bitmap, _Nodes}=CN}) ->
|
||||||
|
case get_1(hash(HashSize, Key), CN, 0, max_depth(HashSize)) of
|
||||||
none ->
|
none ->
|
||||||
not_found;
|
{error, not_found};
|
||||||
{Key, Value} ->
|
{Key, Value} ->
|
||||||
Value;
|
Value;
|
||||||
{list, List} ->
|
{list, List} ->
|
||||||
case get_2(Key, List) of
|
case get_2(Key, List) of
|
||||||
none -> not_found;
|
none ->
|
||||||
{Key, Value} -> Value;
|
{error, not_found};
|
||||||
{_Key, _Value} -> not_found
|
{Key, Value} ->
|
||||||
|
Value;
|
||||||
|
{_Key, _Value} ->
|
||||||
|
{error, not_found}
|
||||||
end;
|
end;
|
||||||
{_Key, _Value} ->
|
{_Key, _Value} ->
|
||||||
not_found
|
{error, not_found}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_1(H, {cnode, Bitmap, Nodes}, L) ->
|
get_1(Hash, {cnode, Bitmap, Nodes}, L, M) when L =< M ->
|
||||||
Bit = bitpos(H, L),
|
Bit = bitpos(Hash, L),
|
||||||
case exists(Bit, Bitmap) of
|
case exists(Bit, Bitmap) of
|
||||||
true -> get_1(H, ba_get(index(Bit, Bitmap), Nodes), L + 5);
|
true ->
|
||||||
false -> none
|
get_1(Hash, ba_get(index(Bit, Bitmap), Nodes), L + 5, M);
|
||||||
|
false ->
|
||||||
|
none
|
||||||
end;
|
end;
|
||||||
get_1(_H, {snode, Key, Value}, _L) ->
|
get_1(_Hash, _, L, M) when L > M ->
|
||||||
|
none;
|
||||||
|
get_1(_Hash, {snode, Key, Value}, _L, _M) ->
|
||||||
{Key, Value};
|
{Key, Value};
|
||||||
get_1(_H, {lnode, List}, _L) when is_list(List) ->
|
get_1(_Hash, {lnode, List}, _L, _M)
|
||||||
|
when is_list(List) ->
|
||||||
{list, List}.
|
{list, List}.
|
||||||
|
|
||||||
get_2(_Key, []) ->
|
get_2(_Key, []) ->
|
||||||
|
@ -165,96 +186,84 @@ get_2(Key, [{Key, Value} | _Rest]) ->
|
||||||
get_2(Key, [{_DifferentKey, _Value} | Rest]) ->
|
get_2(Key, [{_DifferentKey, _Value} | Rest]) ->
|
||||||
get_2(Key, Rest).
|
get_2(Key, Rest).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%% @doc This function converts the Key - Value list List to a trie.
|
||||||
|
-spec from_list([{any(), any()}]) -> hamt().
|
||||||
|
from_list(List) ->
|
||||||
|
put(List, hamt:new()).
|
||||||
|
|
||||||
from_list(L) ->
|
-spec from_list([{any(), any()}], 32|64|128|160) -> hamt().
|
||||||
put(L, hamt:new()).
|
from_list(List, HashSize) ->
|
||||||
|
put(List, hamt:new(HashSize)).
|
||||||
|
|
||||||
to_list({hamt, _}=T) ->
|
-spec put([{Key::any(), Value::any()}], Hamt1::hamt()) -> Hamt2::hamt().
|
||||||
fold(fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, T, []).
|
put([], {hamt, _, _Node}=T) ->
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
-spec put([{Key, Value}], Hamt1) -> Hamt2 when
|
|
||||||
Key :: binary(),
|
|
||||||
Value :: binary(),
|
|
||||||
Hamt1 :: hamt(),
|
|
||||||
Hamt2 :: hamt().
|
|
||||||
|
|
||||||
put([], {hamt, _Node}=T) ->
|
|
||||||
T;
|
T;
|
||||||
put([{Key, Value} | Rest], {hamt, _Node}=T) ->
|
put([{Key, Value} | Rest], {hamt, _, _Node}=T) ->
|
||||||
put(Rest, put(Key, Value, T)).
|
put(Rest, put(Key, Value, T)).
|
||||||
|
|
||||||
-spec put(Key, Value, Hamt1) -> Hamt2 when
|
%% @doc This function converts the trie to a list representation.
|
||||||
Key :: binary(),
|
to_list({hamt, _, _}=T) ->
|
||||||
Value :: binary(),
|
fold(fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, T, []).
|
||||||
Hamt1 :: hamt(),
|
|
||||||
Hamt2 :: hamt().
|
|
||||||
|
|
||||||
put(Key, Value, {hamt, nil})
|
%% @doc This function stores a Key - Value pair in a trie. If the Key
|
||||||
when is_binary(Key), is_binary(Value) ->
|
%% already exists in Hamt1, the associated value is replaced by Value.
|
||||||
{hamt, {snode, Key, Value}};
|
-spec put(Key::any(), Value::any(), Hamt1::hamt()) -> Hamt2::hamt().
|
||||||
put(Key, Value, {hamt, Node})
|
put(Key, Value, {hamt, HashSize, nil}) ->
|
||||||
when is_binary(Key), is_binary(Value) ->
|
{hamt, HashSize, {snode, Key, Value}};
|
||||||
{hamt, put_1(hash(Key), Key, Value, Node, 0)}.
|
put(Key, Value, {hamt, HashSize, Node}) ->
|
||||||
|
{hamt, HashSize, put_1(hash(HashSize, Key), Key, Value, Node, 0, max_depth(HashSize))}.
|
||||||
|
|
||||||
put_1(H, Key, Value, {cnode, Bitmap, Nodes}, L) when is_integer(L), L =< 30 ->
|
put_1(Hash, Key, Value, {cnode, Bitmap, Nodes}, L, M)
|
||||||
Bit = bitpos(H, L),
|
when L =< M ->
|
||||||
|
Bit = bitpos(Hash, L),
|
||||||
Idx = index(Bit, Bitmap),
|
Idx = index(Bit, Bitmap),
|
||||||
case exists(Bit, Bitmap) of
|
case exists(Bit, Bitmap) of
|
||||||
true ->
|
true ->
|
||||||
CN = put_1(H, Key, Value, ba_get(Idx, Nodes), L + 5),
|
CN = put_1(Hash, Key, Value, ba_get(Idx, Nodes), L + 5, M),
|
||||||
{cnode, Bitmap, ba_set(Idx, CN, Nodes)};
|
{cnode, Bitmap, ba_set(Idx, CN, Nodes)};
|
||||||
false ->
|
false ->
|
||||||
{cnode, (Bitmap bor Bit), ba_ins(Idx, {snode, Key, Value}, Nodes)}
|
{cnode, (Bitmap bor Bit), ba_ins(Idx, {snode, Key, Value}, Nodes)}
|
||||||
end;
|
end;
|
||||||
put_1(_H, Key, Value, {snode, Key, _}, _L) ->
|
put_1(_Hash, Key, Value, {snode, Key, _}, _L, _M) ->
|
||||||
{snode, Key, Value};
|
{snode, Key, Value};
|
||||||
put_1(H, Key, Value, {snode, SNKey, SNValue}, L) when is_integer(L), L =< 30 ->
|
put_1(Hash, Key, Value, {snode, SNKey, SNValue}, L, M)
|
||||||
put_1(H, Key, Value, split(SNKey, SNValue, L), L);
|
when L =< M ->
|
||||||
put_1(_H, Key, Value, {snode, _, _}, L) when L > 30 ->
|
CN = {cnode, bitpos(Hash, L), [{snode, SNKey, SNValue}]},
|
||||||
{lnode, [{Key, Value}]};
|
put_1(Hash, Key, Value, CN, L, M);
|
||||||
put_1(_H, Key, Value, {lnode, List}, _L) when is_list(List) ->
|
put_1(_Hash, Key1, Value1, {snode, Key2, Value2}, L, M)
|
||||||
|
when L > M ->
|
||||||
|
{lnode, [{Key1, Value1}, {Key2, Value2}]};
|
||||||
|
put_1(_Hash, Key, Value, {lnode, List}, _L, _M)
|
||||||
|
when is_list(List) ->
|
||||||
{lnode, [{Key, Value} | List]}.
|
{lnode, [{Key, Value} | List]}.
|
||||||
|
|
||||||
split(SNKey, SNValue, L) ->
|
%% @doc This function erases a given key and its value from a trie.
|
||||||
{cnode, bitpos(hash(SNKey), L), [{snode, SNKey, SNValue}]}.
|
-spec delete(Key::any(), Hamt1::hamt()) -> Hamt2::hamt().
|
||||||
|
delete(_Key, {hamt, HashSize, nil}) ->
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
{hamt, HashSize, nil};
|
||||||
|
delete(Key, {hamt, HashSize, Node}=T) ->
|
||||||
-spec delete(Key, Hamt1) -> Hamt2 when
|
Hash = hash(HashSize, Key),
|
||||||
Key :: binary(),
|
case delete_1(Hash, Key, Node, 0, max_depth(HashSize)) of
|
||||||
Hamt1 :: hamt(),
|
|
||||||
Hamt2 :: hamt().
|
|
||||||
|
|
||||||
delete(Key, {hamt, nil})
|
|
||||||
when is_binary(Key) ->
|
|
||||||
{hamt, nil};
|
|
||||||
delete(Key, {hamt, Node}=T)
|
|
||||||
when is_binary(Key) ->
|
|
||||||
case delete_1(hash(Key), Key, Node, 0) of
|
|
||||||
not_found -> T;
|
not_found -> T;
|
||||||
{snode, Key, _} -> {hamt, nil};
|
{snode, Key, _} -> {hamt, HashSize, nil};
|
||||||
{snode, _, _}=N -> {hamt, N};
|
{snode, _, _}=N -> {hamt, HashSize, N};
|
||||||
{cnode, _, _}=N -> {hamt, N}
|
{cnode, _, _}=N -> {hamt, HashSize, N}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
delete_1(H, Key, {cnode, Bitmap, Nodes}, L)
|
delete_1(Hash, Key, {cnode, Bitmap, Nodes}, L, M)
|
||||||
when is_integer(L), L =< 30 ->
|
when L =< M ->
|
||||||
Bit = bitpos(H, L),
|
Bit = bitpos(Hash, L),
|
||||||
Idx = index(Bit, Bitmap),
|
Idx = index(Bit, Bitmap),
|
||||||
case exists(Bit, Bitmap) of
|
case exists(Bit, Bitmap) of
|
||||||
true ->
|
true ->
|
||||||
case delete_1(H, Key, ba_get(Idx, Nodes), L + 5) of
|
case delete_1(Hash, Key, ba_get(Idx, Nodes), L + 5, M) of
|
||||||
{cnode, _, _}=CN ->
|
{cnode, _, _}=CN ->
|
||||||
{cnode, Bitmap, ba_set(Idx, CN, Nodes)};
|
{cnode, Bitmap, ba_set(Idx, CN, Nodes)};
|
||||||
{snode, Key, _} ->
|
{snode, Key, _} ->
|
||||||
case length(Nodes) of
|
case length(Nodes) of
|
||||||
2 ->
|
2 ->
|
||||||
[{snode, _, _}=SN] = ba_del(Key, Nodes),
|
[N] = ba_del(Key, Nodes), N;
|
||||||
SN;
|
_ ->
|
||||||
false ->
|
|
||||||
{cnode, (Bitmap bxor Bit), ba_del(Key, Nodes)}
|
{cnode, (Bitmap bxor Bit), ba_del(Key, Nodes)}
|
||||||
end;
|
end;
|
||||||
{snode, _, _}=SN ->
|
{snode, _, _}=SN ->
|
||||||
|
@ -270,43 +279,43 @@ delete_1(H, Key, {cnode, Bitmap, Nodes}, L)
|
||||||
false ->
|
false ->
|
||||||
not_found
|
not_found
|
||||||
end;
|
end;
|
||||||
delete_1(_H, Key, {snode, Key, _}=SN, _L) ->
|
delete_1(_Hash, _Key, {cnode, _Bitmap, _Nodes}, L, M)
|
||||||
SN;
|
when L > M ->
|
||||||
delete_1(_H, _Key, {snode, _, _}, _L) ->
|
|
||||||
not_found;
|
not_found;
|
||||||
delete_1(_H, Key, {lnode, List}, _L) ->
|
delete_1(_Hash, Key, {snode, Key, _}=SN, _L, _M) ->
|
||||||
case length(List) > 2 of
|
SN;
|
||||||
true ->
|
delete_1(_Hash, _Key, {snode, _, _}, _L, _M) ->
|
||||||
{lnode, lists:filter(fun({snode, K, _}) when K =:= Key -> true;
|
not_found;
|
||||||
({snode, _, _}) -> false end,
|
delete_1(_Hash, Key, {lnode, List}, _L, _M)
|
||||||
List)};
|
when length(List) > 2 ->
|
||||||
false ->
|
{lnode, lists:filter(fun({snode, K, _}) when K =:= Key -> true;
|
||||||
{snode, Key, lists:keyfind(Key, 2, List)}
|
({snode, _, _}) -> false end,
|
||||||
end.
|
List)};
|
||||||
|
delete_1(_Hash, Key, {lnode, List}, _L, _M) ->
|
||||||
|
{snode, Key, lists:keyfind(Key, 2, List)}.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%% @doc Map calls Fun on successive keys and values of Hamt1 to return a new
|
||||||
|
%% value for each key. The evaluation order is undefined.
|
||||||
-spec map(Function, Hamt1) -> Hamt2 when
|
-spec map(Function, Hamt1) -> Hamt2 when % TODO
|
||||||
Function :: fun((K :: term(), V1 :: term()) -> V2 :: term()),
|
Function :: fun((K :: term(), V1 :: term()) -> V2 :: term()),
|
||||||
Hamt1 :: hamt(),
|
Hamt1 :: hamt(), Hamt2 :: hamt().
|
||||||
Hamt2 :: hamt().
|
map(F, {hamt, HashSize, _}=T)
|
||||||
|
when is_function(F, 2) ->
|
||||||
map(F, {hamt, _}=T) when is_function(F, 2) ->
|
{hamt, HashSize, map_1(F, T)}.
|
||||||
{map_1(F, T)}.
|
|
||||||
|
|
||||||
map_1(_, nil) -> nil;
|
map_1(_, nil) -> nil;
|
||||||
map_1(F, {K, V, Smaller, Larger}) ->
|
map_1(F, {K, V, Smaller, Larger}) ->
|
||||||
{K, F(K, V), map_1(F, Smaller), map_1(F, Larger)}.
|
{K, F(K, V), map_1(F, Smaller), map_1(F, Larger)}.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%% @doc Calls Fun on successive keys and values of a trie together with an
|
||||||
|
%% extra argument Acc (short for accumulator). Fun must return a new
|
||||||
-spec fold(Function, Hamt, Acc) -> Hamt when
|
%% accumulator which is passed to the next call. Acc0 is returned if
|
||||||
Function :: fun((K :: term(), V :: term()) -> V2 :: term()),
|
%% the list is empty. The evaluation order is undefined.
|
||||||
Hamt :: hamt(),
|
-spec fold(Fun, Hamt, Acc) -> Hamt when
|
||||||
Acc :: any().
|
Fun :: fun((K :: term(), V :: term(), Acc :: any()) -> Acc2 :: any()),
|
||||||
|
Hamt :: hamt(), Acc :: any().
|
||||||
fold(Function, {hamt, Node}, Acc) ->
|
fold(Fun, {hamt, _, Node}, Acc) ->
|
||||||
fold_1(Function, Acc, Node).
|
fold_1(Fun, Acc, Node).
|
||||||
|
|
||||||
fold_1(F, Acc, {snode, Key, Value}) ->
|
fold_1(F, Acc, {snode, Key, Value}) ->
|
||||||
F(Key, Value, Acc);
|
F(Key, Value, Acc);
|
||||||
|
@ -316,10 +325,14 @@ fold_1(F, Acc, {cnode, _, [Node]}) ->
|
||||||
fold_1(F, Acc, Node);
|
fold_1(F, Acc, Node);
|
||||||
fold_1(F, Acc, {cnode, Bitmap, [Node | Nodes]}) ->
|
fold_1(F, Acc, {cnode, Bitmap, [Node | Nodes]}) ->
|
||||||
fold_1(F, fold_1(F, Acc, Node), {cnode, Bitmap, Nodes});
|
fold_1(F, fold_1(F, Acc, Node), {cnode, Bitmap, Nodes});
|
||||||
fold_1(F, Acc, {lnode, Nodes}) ->
|
fold_1(_F, Acc, {lnode, []}) ->
|
||||||
lists:foldl(F, Acc, Nodes).
|
Acc;
|
||||||
|
fold_1(F, Acc, {lnode, [{Key, Value}]}) ->
|
||||||
|
F(Key, Value, Acc);
|
||||||
|
fold_1(F, Acc, {lnode, [{Key, Value} | KVPs]}) ->
|
||||||
|
fold_1(F, F(Key, Value, Acc), {lnode, KVPs}).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%% Below here are other supporting functions, not public API.
|
||||||
|
|
||||||
ba_get(I, Nodes)
|
ba_get(I, Nodes)
|
||||||
when I =< 32, erlang:length(Nodes) =< 32 ->
|
when I =< 32, erlang:length(Nodes) =< 32 ->
|
||||||
|
@ -346,8 +359,6 @@ ba_del(Key, Nodes) ->
|
||||||
({lnode, _}) -> true
|
({lnode, _}) -> true
|
||||||
end, Nodes).
|
end, Nodes).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
mask(Hash, Shift) ->
|
mask(Hash, Shift) ->
|
||||||
(Hash bsr Shift) band 2#11111.
|
(Hash bsr Shift) band 2#11111.
|
||||||
|
|
||||||
|
@ -355,78 +366,60 @@ bitpos(Hash, Shift) ->
|
||||||
1 bsl mask(Hash, Shift).
|
1 bsl mask(Hash, Shift).
|
||||||
|
|
||||||
index(Bit, Bitmap) ->
|
index(Bit, Bitmap) ->
|
||||||
bitpop:count(Bitmap band (Bit - 1)) + 1. % Arrays start with index 1, not 0
|
bitpop:count(Bitmap band (Bit - 1)) + 1.
|
||||||
|
|
||||||
exists(Bit, Bitmap) ->
|
exists(Bit, Bitmap) ->
|
||||||
(Bitmap band Bit) =:= Bit.
|
(Bitmap band Bit) =:= Bit.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
max_depth(B)
|
||||||
|
when B =:= 32 ->
|
||||||
hash(Key) when is_binary(Key) ->
|
30;
|
||||||
erlang:phash2(Key).
|
max_depth(B)
|
||||||
|
when B =:= 64 ->
|
||||||
|
30;
|
||||||
|
max_depth(B)
|
||||||
|
when B > 64 ->
|
||||||
|
(B div 5) * 5.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
|
||||||
create_a_hamt_test_() ->
|
hamt_basics_test_() ->
|
||||||
[?_assertEqual({hamt, nil}, hamt:new()),
|
[ ?_assertEqual(true, hamt:is_empty(hamt:new())),
|
||||||
?_assertEqual(true, hamt:is_empty(hamt:new())),
|
|
||||||
?_assertEqual(false, hamt:is_empty(hamt:put(<<"k">>, <<"v">>, hamt:new()))),
|
?_assertEqual(false, hamt:is_empty(hamt:put(<<"k">>, <<"v">>, hamt:new()))),
|
||||||
?_assertEqual(<<"v">>, hamt:get(<<"k">>, hamt:put(<<"k">>, <<"v">>, hamt:new())))].
|
?_assertEqual(<<"v">>, hamt:get(<<"k">>, hamt:put(<<"k">>, <<"v">>, hamt:new()))),
|
||||||
|
?_assertEqual({error, not_found}, hamt:get(<<"x">>, hamt:put(<<"k">>, <<"v">>, hamt:new())))].
|
||||||
|
|
||||||
put_causes_split_root_snode_test() ->
|
put_cases_split_root_snode_test() ->
|
||||||
?assertEqual(hamt:put(<<"k2">>, <<"v2">>, {hamt,{snode,<<"k1">>,<<"v1">>}}),
|
?assertEqual(hamt:put(<<"k2">>, <<"v2">>, hamt:put(<<"k1">>, <<"v1">>, hamt:new())),
|
||||||
{hamt,{cnode,4456448,
|
hamt:from_list([{<<"k1">>, <<"v1">>}, {<<"k2">>, <<"v2">>}])).
|
||||||
[{snode,<<"k1">>,<<"v1">>},{snode,<<"k2">>,<<"v2">>}]}}).
|
|
||||||
|
|
||||||
put_causes_2_splits_test() ->
|
|
||||||
?assertEqual(hamt:put(<<5>>,<<5>>,{hamt,{cnode,17563904,
|
|
||||||
[{snode,<<3>>,<<3>>},
|
|
||||||
{snode,<<1>>,<<1>>},
|
|
||||||
{snode,<<2>>,<<2>>},
|
|
||||||
{snode,<<4>>,<<4>>}]}}),
|
|
||||||
{hamt,
|
|
||||||
{cnode,17563904,
|
|
||||||
[{snode,<<3>>,<<3>>},
|
|
||||||
{snode,<<1>>,<<1>>},
|
|
||||||
{cnode,131072,
|
|
||||||
[{cnode,142606336,
|
|
||||||
[{snode,<<2>>,<<2>>},
|
|
||||||
{snode,<<5>>,<<5>>}]}]},
|
|
||||||
{snode,<<4>>,<<4>>}]}}).
|
|
||||||
|
|
||||||
put_existing_key_replaces_value_test() ->
|
put_existing_key_replaces_value_test() ->
|
||||||
?assertEqual(hamt:put(<<"k1">>, <<"v'">>,
|
?assertEqual(hamt:get(<<"k1">>, hamt:put(<<"k1">>, <<"v'">>, hamt:put(<<"k1">>, <<"v1">>, hamt:new()))), <<"v'">>).
|
||||||
{hamt,{cnode,4456448,
|
|
||||||
[{snode,<<"k1">>,<<"v1">>},{snode,<<"k2">>,<<"v2">>}]}}),
|
|
||||||
{hamt,{cnode,4456448,
|
|
||||||
[{snode,<<"k1">>,<<"v'">>},{snode,<<"k2">>,<<"v2">>}]}}).
|
|
||||||
|
|
||||||
del_from_empty_trie_test() ->
|
del_from_empty_trie_test() ->
|
||||||
?assertEqual(hamt:delete(<<"k1">>, {hamt, nil}), {hamt, nil}).
|
?assertEqual(hamt:delete(<<"k1">>, hamt:new()), hamt:new()).
|
||||||
|
|
||||||
del_last_key_in_trie_test() ->
|
del_last_key_in_trie_test() ->
|
||||||
?assertEqual(hamt:delete(<<"k1">>, {hamt,{snode,<<"k1">>,<<"v1">>}}), {hamt, nil}).
|
?assertEqual(hamt:delete(<<"k1">>, hamt:put(<<"k1">>, <<"v1">>, hamt:new())), hamt:new()).
|
||||||
|
|
||||||
del_one_of_many_keys_test() ->
|
del_one_of_many_keys_test() ->
|
||||||
?assertEqual(hamt:delete(<<"k1">>,
|
N = 100,
|
||||||
{hamt,{cnode,4456448,
|
H1 = hamt:from_list([{random:uniform(N), <<X>>} || X <- lists:seq(1,N)]),
|
||||||
[{snode,<<"k1">>,<<"v1">>},
|
H2 = hamt:put(<<"k">>, <<"v">>, H1),
|
||||||
{snode,<<"k2">>,<<"v2">>}]}}),
|
H3 = hamt:delete(<<"k">>, H2),
|
||||||
{hamt,{snode,<<"k2">>,<<"v2">>}}).
|
?assertEqual(hamt:get(<<"k">>, H2), <<"v">>),
|
||||||
|
?assertEqual(lists:usort(hamt:to_list(H3)), lists:usort(hamt:to_list(H1))),
|
||||||
|
?assertEqual(hamt:get(<<"k">>, H3), {error, not_found}).
|
||||||
|
|
||||||
del_causes_cascading_cnode_collapse_test() ->
|
del_causes_cascading_cnode_collapse_test() ->
|
||||||
?assertEqual(hamt:delete(<<5>>, hamt:put([{<<X>>, <<X>>} || X <- lists:seq(1,6)], hamt:new())),
|
H = hamt:put([{<<X>>, <<X>>} || X <- lists:seq(1,6)], hamt:new()),
|
||||||
{hamt,{cnode,17629440,
|
?assertNotEqual(lists:usort(hamt:to_list(hamt:delete(<<5>>, H))),
|
||||||
[{snode,<<3>>,<<3>>},
|
lists:usort(hamt:to_list(H))).
|
||||||
{snode,<<6>>,<<6>>},
|
|
||||||
{snode,<<1>>,<<1>>},
|
|
||||||
{snode,<<2>>,<<2>>},
|
|
||||||
{snode,<<4>>,<<4>>}]}}).
|
|
||||||
|
|
||||||
put_lots_test() ->
|
put_lots_test() ->
|
||||||
KVPs = [{<<X>>, <<X>>} || X <- lists:seq(1,10000)],
|
N = 10000, hamt:from_list([{random:uniform(N), <<X>>} || X <- lists:seq(1,N)]).
|
||||||
hamt:put(KVPs, hamt:new()).
|
|
||||||
|
|
||||||
%% test() ->
|
%% test() ->
|
||||||
%% test(500).
|
%% test(500).
|
||||||
|
|
Loading…
Reference in a new issue