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:
Gregory Burd 2013-02-24 16:30:18 -05:00
parent 9a9bebce57
commit 61677cd1b8

View file

@ -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).