From 7b5b040f640a5cfb3e0d5a38277d3974943f9a36 Mon Sep 17 00:00:00 2001 From: Gregory Burd Date: Thu, 21 Feb 2013 16:02:32 -0500 Subject: [PATCH] Initial import. --- .gitignore | 6 + Makefile | 58 +++++++ rebar.config | 33 ++++ src/bitpop.erl | 65 +++++++ src/hamt.app.src | 12 ++ src/hamt.erl | 442 +++++++++++++++++++++++++++++++++++++++++++++++ src/hamt_app.erl | 16 ++ src/hamt_sup.erl | 27 +++ 8 files changed, 659 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 rebar.config create mode 100644 src/bitpop.erl create mode 100644 src/hamt.app.src create mode 100644 src/hamt.erl create mode 100644 src/hamt_app.erl create mode 100644 src/hamt_sup.erl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f9d737 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.eunit/* +deps/* +ebin +priv/*.so +*.o +*.beam diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13af30a --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +TARGET= hamt + +REBAR= /usr/bin/env rebar +ERL= /usr/bin/env erl +DIALYZER= /usr/bin/env dialyzer +REBAR= /usr/bin/env rebar +ifdef suites + SUITE_OPTION := suites=$(suites) +endif +ifdef tests + TESTS_OPTION := tests=$(tests) +endif +EUNIT_OPTIONS := $(SUITE_OPTION) $(TESTS_OPTION) + +.PHONY: deps test + +all: deps compile + +deps: + $(REBAR) get-deps + +compile: deps + $(REBAR) compile + +clean: + $(REBAR) clean + +distclean: clean + $(REBAR) delete-deps + +eunit: test + +test: compile + $(REBAR) skip_deps=true $(EUNIT_OPTIONS) eunit + +console: compile + erl -pa ebin deps/*/ebin + +plt: compile + @$(DIALYZER) --build_plt --output_plt .$(TARGET).plt \ + -pa deps/plain_fsm/ebin \ + deps/plain_fsm/ebin \ + --apps kernel stdlib + +analyze: compile + $(DIALYZER) --plt .$(TARGET).plt \ + -pa deps/plain_fsm/ebin \ + -pa deps/ebloom/ebin \ + ebin + +repl: + $(ERL) -pz deps/*/ebin -pa ebin + +eunit-repl: + erl -pa .eunit -pz deps/*/ebin -pz ebin -exec 'cd(".eunit").' + +gdb-eunit-repl: + USE_GDB=1 erl -pa .eunit -pz deps/*/ebin -pz ebin -exec 'cd(".eunit").' diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..d72b27b --- /dev/null +++ b/rebar.config @@ -0,0 +1,33 @@ +%%% -*- mode: erlang -*- + +{require_otp_vsn, "R15"}. + +{cover_enabled, true}. + +%{clean_files, ["*.eunit", "ebin/*.beam"]}. +{eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. + +{erl_opts, [%{d,'DEBUG',true}, + debug_info, + fail_on_warning, + warn_unused_vars, + warn_export_all, + warn_shadow_vars, + warn_unused_import, + warn_unused_function, + warn_bif_clash, + warn_unused_record, + warn_deprecated_function, + warn_obsolete_guard, + warn_export_vars, + warn_exported_vars, + warn_untyped_record + %warn_missing_spec, + %strict_validation + %{parse_transform, lager_transform}, + ]}. + +{xref_checks, [undefined_function_calls]}. + +{deps, [ + ]}. diff --git a/src/bitpop.erl b/src/bitpop.erl new file mode 100644 index 0000000..748d92e --- /dev/null +++ b/src/bitpop.erl @@ -0,0 +1,65 @@ +%% -*- coding: utf-8 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright (C) 2013 Gregory Burd. All Rights Reserved. +%% +%% The contents of this file are subject to the Mozilla Public License, +%% Version 2, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Mozilla Public License along with this software. If not, it can be +%% retrieved online at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(bitpop). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-export([count/1]). + +count(0) -> 0; +count(X) when is_integer(X), X > 0 -> count(X, 0). +count(0, Acc) -> Acc; +count(X, Acc) -> count((X band (X - 1)), (Acc + 1)). + +-ifdef(TEST). + +bitpop_test_() -> + [?_assertEqual(0, count(0)), + ?_assertEqual(1, count(1)), + ?_assertEqual(2, count(3)), + ?_assertEqual(3, count(7)), + ?_assertEqual(4, count(15)), + ?_assertEqual(5, count(31)), + ?_assertEqual(6, count(63)), + ?_assertEqual(7, count(127)), + ?_assertEqual(8, count(255)), + ?_assertEqual(1, count(4)), + ?_assertEqual(1, count(8)), + ?_assertEqual(1, count(16)), + ?_assertEqual(1, count(32)), + ?_assertEqual(1, count(64)), + ?_assertEqual(1, count(128)), + ?_assertEqual(1, count(256)), + ?_assertEqual(1, count(512)), + ?_assertEqual(1, count(1024)), + ?_assertEqual(1, count(2048)), + ?_assertEqual(1, count(16#FFFF + 1)), + ?_assertEqual(19, count(16#FFFFE)), + ?_assertEqual(1, count(16#FFFFF + 1)), + ?_assertEqual(23, count(16#FFFFFE)), + ?_assertEqual(1, count(16#FFFFF + 1)), + ?_assertEqual(27, count(16#FFFFFFE)), + ?_assertEqual(1, count(16#FFFFF + 1)), + ?_assertEqual(31, count(16#FFFFFFFE)), + ?_assertException(error, function_clause, count(-1))]. + +-endif. diff --git a/src/hamt.app.src b/src/hamt.app.src new file mode 100644 index 0000000..5968e4c --- /dev/null +++ b/src/hamt.app.src @@ -0,0 +1,12 @@ +{application, hamt, + [ + {description, "Hash Array Mapped Tries"}, + {vsn, "1.0.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {hamt_app, []}}, + {env, []} + ]}. diff --git a/src/hamt.erl b/src/hamt.erl new file mode 100644 index 0000000..52ee4b0 --- /dev/null +++ b/src/hamt.erl @@ -0,0 +1,442 @@ +%% -*- coding: utf-8 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright (C) 2013 Gregory Burd. All Rights Reserved. +%% +%% The contents of this file are subject to the Mozilla Public License, +%% Version 2, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Mozilla Public License along with this software. If not, it can be +%% retrieved online at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% ========================================================================= +%% Ideal Hash Array Mapped Tries: an Erlang functional datatype +%% +%% The Hash Array Mapped Trie (HAMT) is based on the simple notion of hashing a +%% key and storing the key in a trie based on this hash value. The AMT is used +%% to implement the required structure e#ciently. The Array Mapped Trie (AMT) +%% is a versatile data structure and yields attractive alternative to +%% contemporary algorithms in many applications. Here I describe how it is used +%% to develop Hash Trees with near ideal characteristics that avoid the +%% traditional problem, setting the size of the initial root hash table or +%% incurring the high cost of dynamic resizing to achieve an acceptable +%% performance. +%% +%% Based on the paper "Ideal Hash Tries" by Phil Bagwell [2000]. +%% @ARTICLE{Bagwell01idealhash, +%% author = {Phil Bagwell}, +%% title = {Ideal Hash Trees}, +%% journal = {Es Grands Champs}, +%% year = {2001}, +%% volume = {1195} +%% } +%% http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.6279 +%% http://lampwww.epfl.ch/papers/idealhashtrees.pdf +%% http://en.wikipedia.org/wiki/Hash_array_mapped_trie +%% ------------------------------------------------------------------------- +%% Operations: +%% +%% - new(): returns empty hamt. +%% +%% - is_empty(T): returns 'true' if T is an empty hamt, and 'false' +%% otherwise. +%% +%% - get(K, T): retreives the value stored with key K in hamt T or +%% `not_found' if the key is not present in the hamt. +%% +%% - put(K, V, T): inserts key K with value V into hamt T; if the key +%% is not present in the hamt, otherwise updates key X to value V in +%% T. Returns the new hamt. +%% +%% - delete(K, T): removes key K from hamt T; returns new hamt. Assumes +%% that the key is present in the hamt. +%% +%% - map(F, T): maps the function F(K, V) -> V' to all key-value pairs +%% of the hamt T and returns a new hamt T' with the same set of keys +%% as T and the new set of values V'. +%% +%% - fold(F, T, Acc): applies the function F(K, V, Acc) -> V' to all +%% key-value pairs of the hamt T and returns the accumulated result. +%% +-module(hamt). +-compile({no_auto_import,[put/2]}). + +-ifdef(PULSE). +-compile({parse_transform, pulse_instrument}). +-endif. + +-ifdef(TEST). +-ifdef(EQC). +%include_lib("eqc/include/eqc.hrl"). +%include_lib("eqc/include/eqc_fsm.hrl"). +-endif. +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-export([new/0, is_empty/1, get/2, put/2, put/3, delete/2, + map/2, fold/3, + from_list/1, to_list/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The Hamt data structure consists of: +%% - {hamt, nil | {SNode, CNode, LNode} +%% - {snode, Key::binary(), Value::binary()} +%% - {cnode, Bitmap, Branch} +%% - {lnode, [snode]} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Some types. + +-export_type([hamt/0]). + +-type hamt_snode() :: {snode, binary(), binary()}. +-type hamt_lnode() :: {lnode, [hamt_snode()]}. +-type hamt_cnode() :: {cnode, non_neg_integer(), [hamt_snode() | hamt_cnode() | hamt_lnode()]}. +-opaque hamt() :: {hamt, non_neg_integer(), nil | hamt_cnode()}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec new() -> hamt(). + +new() -> + {hamt, nil}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec is_empty(Hamt) -> boolean() when + Hamt :: hamt(). + +is_empty({hamt, nil}) -> + true; +is_empty(_) -> + false. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec get(Key, Hamt) -> not_found | Value when + Key :: binary(), + Value :: binary(), + Hamt :: hamt(). + +get(_Key, {hamt, nil}) -> + not_found; +get(Key, {hamt, {snode, Key, Value}}) -> + Value; +get(Key, {hamt, {cnode, _Bitmap, _Nodes}=CN}) -> + case get_1(hash(Key), CN, 0) of + none -> + not_found; + {Key, Value} -> + Value; + {list, List} -> + case get_2(Key, List) of + none -> not_found; + {Key, Value} -> Value; + {_Key, _Value} -> not_found + end; + {_Key, _Value} -> + not_found + end. + +get_1(H, {cnode, Bitmap, Nodes}, L) -> + Bit = bitpos(H, L), + case exists(Bit, Bitmap) of + true -> get_1(H, ba_get(index(Bit, Bitmap), Nodes), L + 5); + false -> none + end; +get_1(_H, {snode, Key, Value}, _L) -> + {Key, Value}; +get_1(_H, {lnode, List}, _L) when is_list(List) -> + {list, List}. + +get_2(_Key, []) -> + none; +get_2(Key, [{Key, Value} | _Rest]) -> + {Key, Value}; +get_2(Key, [{_DifferentKey, _Value} | Rest]) -> + get_2(Key, Rest). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +from_list(L) -> + put(L, hamt:new()). + +to_list({hamt, _}=T) -> + fold(fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, T, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec put([{Key, Value}], Hamt1) -> Hamt2 when + Key :: binary(), + Value :: binary(), + Hamt1 :: hamt(), + Hamt2 :: hamt(). + +put([], {hamt, _Node}=T) -> + T; +put([{Key, Value} | Rest], {hamt, _Node}=T) -> + put(Rest, put(Key, Value, T)). + +-spec put(Key, Value, Hamt1) -> Hamt2 when + Key :: binary(), + Value :: binary(), + Hamt1 :: hamt(), + Hamt2 :: hamt(). + +put(Key, Value, {hamt, nil}) + when is_binary(Key), is_binary(Value) -> + {hamt, {snode, Key, Value}}; +put(Key, Value, {hamt, Node}) + when is_binary(Key), is_binary(Value) -> + {hamt, put_1(hash(Key), Key, Value, Node, 0)}. + +put_1(H, Key, Value, {cnode, Bitmap, Nodes}, L) when is_integer(L), L =< 30 -> + Bit = bitpos(H, L), + Idx = index(Bit, Bitmap), + case exists(Bit, Bitmap) of + true -> + CN = put_1(H, Key, Value, ba_get(Idx, Nodes), L + 5), + {cnode, Bitmap, ba_set(Idx, CN, Nodes)}; + false -> + {cnode, (Bitmap bor Bit), ba_ins(Idx, {snode, Key, Value}, Nodes)} + end; +put_1(_H, Key, Value, {snode, Key, _}, _L) -> + {snode, Key, Value}; +put_1(H, Key, Value, {snode, SNKey, SNValue}, L) when is_integer(L), L =< 30 -> + put_1(H, Key, Value, split(SNKey, SNValue, L), L); +put_1(_H, Key, Value, {snode, _, _}, L) when L > 30 -> + {lnode, [{Key, Value}]}; +put_1(_H, Key, Value, {lnode, List}, _L) when is_list(List) -> + {lnode, [{Key, Value} | List]}. + +split(SNKey, SNValue, L) -> + {cnode, bitpos(hash(SNKey), L), [{snode, SNKey, SNValue}]}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec delete(Key, Hamt1) -> Hamt2 when + Key :: binary(), + 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; + delete -> {hamt, nil}; + N -> {hamt, N} + end. + +delete_1(H, Key, {cnode, Bitmap, Nodes}=CNode, L) + when is_integer(L), L =< 30 -> + Bit = bitpos(H, L), + Idx = index(Bit, Bitmap), + case exists(Bit, Bitmap) of + true -> + case delete_1(H, Key, ba_get(Idx, Nodes), L + 5) of + not_found -> not_found; + delete -> delete_2(Key, Bit, CNode); + N -> {cnode, Bitmap, ba_set(Idx, N, Nodes)} + end; + false -> + not_found + end; +delete_1(_H, Key, {snode, Key, _}, _L) -> + delete; +delete_1(_H, _Key, {snode, _, _}, _L) -> + not_found; +delete_1(_H, Key, {lnode, List}, _L) -> + case length(List) > 2 of + true -> + {lnode, lists:filter(fun({snode, K, _}) when K =:= Key -> true; + ({snode, _, _}) -> false end, + List)}; + false -> + {snode, Key, lists:keyfind(Key, 2, List)} + end. + +%% @doc This CNode only has 2 elements in it and one is about to be +%% be deleted, time to collapse this CNode into an SNode. +delete_2(Key, _Bit, {cnode, _Bitmap, Nodes}) + when length(Nodes) =:= 2 -> + [{snode, _, _}=SN] = ba_del(Key, Nodes), + SN; +%% @doc Remove the right key and update the bitmap +delete_2(Key, Bit, {cnode, Bitmap, Nodes}) -> + {cnode, (Bitmap bxor Bit), ba_del(Key, Nodes)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec map(Function, Hamt1) -> Hamt2 when + Function :: fun((K :: term(), V1 :: term()) -> V2 :: term()), + Hamt1 :: hamt(), + Hamt2 :: hamt(). + +map(F, {hamt, _}=T) when is_function(F, 2) -> + {map_1(F, T)}. + +map_1(_, nil) -> nil; +map_1(F, {K, V, Smaller, Larger}) -> + {K, F(K, V), map_1(F, Smaller), map_1(F, Larger)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec fold(Function, Hamt, Acc) -> Hamt when + Function :: fun((K :: term(), V :: term()) -> V2 :: term()), + Hamt :: hamt(), + Acc :: any(). + +fold(Function, {hamt, Node}, Acc) -> + fold_1(Function, Acc, Node). + +fold_1(F, Acc, {snode, Key, Value}) -> + F(Key, Value, Acc); +fold_1(_F, Acc, {cnode, _, []}) -> + Acc; +fold_1(F, Acc, {cnode, _, [Node]}) -> + fold_1(F, Acc, Node); +fold_1(F, Acc, {cnode, Bitmap, [Node | Nodes]}) -> + fold_1(F, fold_1(F, Acc, Node), {cnode, Bitmap, Nodes}); +fold_1(F, Acc, {lnode, Nodes}) -> + lists:foldl(F, Acc, Nodes). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ba_get(I, Nodes) + when I =< 32, erlang:length(Nodes) =< 32 -> + lists:nth(I, Nodes). +ba_set(1, V, [_|T]=Nodes) + when erlang:length(Nodes) =< 32 -> + [V|T]; +ba_set(I, V, [H|T]=Nodes) + when I =< 32, erlang:length(Nodes) =< 32 -> + [H|ba_set(I-1, V, T)]. +ba_ins(1, V, [H|T]=Nodes) + when erlang:length(Nodes) =< 32 -> + [V|[H|T]]; +ba_ins(I, V, [H|T]=Nodes) + when I =< 32, erlang:length(Nodes) =< 32 -> + [H|ba_ins(I-1, V, T)]; +ba_ins(1, V, [H]) -> [H, V]; +ba_ins(2, V, [H]) -> [V, H]; +ba_ins(_I, V, []) -> [V]. +ba_del(Key, Nodes) -> + lists:filter(fun({snode, K, _}) when K =:= Key -> false; + ({snode, _, _}) -> true; + ({cnode, _, _}) -> true; + ({lnode, _}) -> true + end, Nodes). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mask(Hash, Shift) -> + (Hash bsr Shift) band 2#11111. + +bitpos(Hash, Shift) -> + 1 bsl mask(Hash, Shift). + +index(Bit, Bitmap) -> + bitpop:count(Bitmap band (Bit - 1)) + 1. % Arrays start with index 1, not 0 + +exists(Bit, Bitmap) -> + (Bitmap band Bit) =:= Bit. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +hash(Key) when is_binary(Key) -> + erlang:phash2(Key). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-ifdef(TEST). + +create_a_hamt_test_() -> + [?_assertEqual({hamt, nil}, hamt:new()), + ?_assertEqual(true, hamt:is_empty(hamt:new())), + ?_assertEqual(false, hamt:is_empty(hamt:put(<<"k">>, <<"v">>, hamt:new()))), + ?_assertEqual(<<"v">>, hamt:get(<<"k">>, hamt:put(<<"k">>, <<"v">>, hamt:new())))]. + +put_causes_split_root_snode_test() -> + ?assertEqual(hamt:put(<<"k2">>, <<"v2">>, {hamt,{snode,<<"k1">>,<<"v1">>}}), + {hamt,{cnode,4456448, + [{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() -> + ?assertEqual(hamt:put(<<"k1">>, <<"v'">>, + {hamt,{cnode,4456448, + [{snode,<<"k1">>,<<"v1">>},{snode,<<"k2">>,<<"v2">>}]}}), + {hamt,{cnode,4456448, + [{snode,<<"k1">>,<<"v'">>},{snode,<<"k2">>,<<"v2">>}]}}). + +del_from_empty_trie_test() -> + ?assertEqual(hamt:delete(<<"k1">>, {hamt, nil}), {hamt, nil}). + +del_last_key_in_trie_test() -> + ?assertEqual(hamt:delete(<<"k1">>, {hamt,{snode,<<"k1">>,<<"v1">>}}), {hamt, nil}). + +del_one_of_many_keys_test() -> + ?assertEqual(hamt:delete(<<"k1">>, + {hamt,{cnode,4456448, + [{snode,<<"k1">>,<<"v1">>}, + {snode,<<"k2">>,<<"v2">>}]}}), + {hamt,{snode,<<"k2">>,<<"v2">>}}). + +del_causes_cascading_cnode_collapse_test() -> + H = hamt:put([{<>, <>} || X <- lists:seq(1,5)], hamt:new()), + ?assertEqual(hamt:delete(<<5>>, H), + hamt:put([{<>, <>} || X <- lists:seq(1,4)], hamt:new())). + +put_lots_test() -> + KVPs = [{<>, <>} || X <- lists:seq(1,10000)], + hamt:put(KVPs, hamt:new()). + +%% test() -> +%% test(500). + +%% test(NumTimes) -> +%% eqc:quickcheck(eqc:numtests(NumTimes, prop_to_list())). + +%% input_list() -> +%% list({list(int()), int()}). + +%% prop_to_list() -> +%% ?FORALL(Xs, input_list(), +%% equiv_to_orddict(Xs)). + +%% equiv_to_orddict(Xs) -> +%% orddict:from_list(hamt:to_list(Xs)) =:= orddict:from_list(Xs). + +%% from_list(L) -> +%% lists:foldl(fun insert_fun/2, empty(), L). + +%% insert_fun({Key, Value}, Acc) -> +%% put(Key, Value, Acc). + +-endif. diff --git a/src/hamt_app.erl b/src/hamt_app.erl new file mode 100644 index 0000000..02f1997 --- /dev/null +++ b/src/hamt_app.erl @@ -0,0 +1,16 @@ +-module(hamt_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%% =================================================================== +%% Application callbacks +%% =================================================================== + +start(_StartType, _StartArgs) -> + hamt_sup:start_link(). + +stop(_State) -> + ok. diff --git a/src/hamt_sup.erl b/src/hamt_sup.erl new file mode 100644 index 0000000..2d62371 --- /dev/null +++ b/src/hamt_sup.erl @@ -0,0 +1,27 @@ + +-module(hamt_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%% Helper macro for declaring children of supervisor +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +%% =================================================================== +%% API functions +%% =================================================================== + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +init([]) -> + {ok, { {one_for_one, 5, 10}, []} }.