Packages

Erlang/Elixir's missing fast / functional data structures

Current section

Files

Jump to
fds src fds_ralist.erl
Raw

src/fds_ralist.erl

-module(fds_ralist).
-export([new/0, cons/2, head/1, tail/1, lookup/2, update/3]).
-export([foldl/3, count/1]).
%% Core Data Structure: Random-Access List
%%
%% The random-access list is a list (efficient push pop from one end) that also
%% allows for efficient random access and update of its elements.
%%
%% Runtime Complexity Rundown
%%
%% O(1)
%% new()
%% cons(Item, List)
%% head(List)
%% tail(List)
%%
%% O(log(n))
%% lookup(Index, List)
%% update(Index, Item, List)
%% count(List)
%%
%% O(n)
%% foldl(Fun, InitialAcc, List)
%% count(List)
-type ratree(T) :: {T} | {T, ratree(T), ratree(T)}.
-type ranode(T) :: {pos_integer(), ratree(T)}.
-type ralist(T) :: [ranode(T)].
%% TODO:
%% * implement map
%% * make foldl more efficient by using pattern matching
%% * replace tuple of one element with just a single element for the Leaf case
-spec new() -> ralist(term()).
new() -> [].
-spec cons(T, ralist(T)) -> ralist(T).
cons(Elem, [{Weight, T1}, {Weight, T2} | Rest]) ->
[{ Weight*2+1, {Elem, T1, T2} }| Rest];
cons(Elem, RAList) ->
[{1, {Elem}} | RAList].
-spec head(ralist(T)) -> T.
head([]) ->
throw(badarg);
head([{1, {Elem}} | _Rest]) ->
Elem;
head([{_Weight, {Elem,_Tree1,_Tree2}} | _Rest]) ->
Elem.
-spec tail(ralist(T)) -> ralist(T).
tail([]) ->
throw(badarg);
tail([{1, _Tree} | Rest]) ->
Rest;
tail([{Weight, {_Elem, Tree1, Tree2}} | Rest]) ->
[{Weight div 2, Tree1}, {Weight div 2, Tree2} | Rest].
-spec lookup(non_neg_integer(), ralist(T)) -> T.
lookup(_Index, []) ->
erlang:error(badarg);
lookup(Index, [{Weight, Tree} | _Rest]) when Index < Weight ->
lookup_tree(Index, Weight, Tree);
lookup(Index, [{Weight, _Tree} | Rest]) ->
lookup(Index - Weight, Rest).
lookup_tree(0, 1, {Elem}) ->
Elem;
lookup_tree(0, _Weight, {Elem, _Tree1, _Tree2}) ->
Elem;
lookup_tree(Index, Weight, {_Elem, Tree1, _Tree2}) when Index - 1 < Weight div 2 ->
lookup_tree(Index - 1, Weight div 2, Tree1);
lookup_tree(Index, Weight, {_Elem, _Tree1, Tree2}) ->
lookup_tree(Index - 1 - Weight div 2, Weight div 2, Tree2).
-spec update(non_neg_integer(), T, ralist(T)) -> ralist(T).
update(_Index, _Elem, []) ->
erlang:error(badarg);
update(Index, Elem, [{Weight, Tree} | Rest]) when Index < Weight ->
[{Weight, update_tree(Index, Elem, Weight, Tree)} | Rest];
update(Index, Elem, [{Weight, Tree} | Rest]) ->
[{Weight, Tree} | update(Index - Weight, Elem, Rest)].
update_tree(0, NewElem, 1, {_OldElem}) ->
{NewElem};
update_tree(0, NewElem, _Weight, {_OldElem, Tree1, Tree2}) ->
{NewElem, Tree1, Tree2};
update_tree(Index, NewElem, Weight, {Elem, Tree1, Tree2}) when Index - 1 < Weight div 2 ->
{Elem, update_tree(Index - 1, NewElem, Weight div 2, Tree1), Tree2};
update_tree(Index, NewElem, Weight, {Elem, Tree1, Tree2}) ->
{Elem, Tree1, update_tree(Index - 1 - Weight div 2, NewElem, Weight div 2, Tree2)}.
-spec foldl(fun((T, term()) -> term()), term(), ralist(T)) -> term().
foldl(_Fun, Acc, []) ->
Acc;
foldl(Fun, Acc, RAList) ->
foldl(Fun, Fun(head(RAList), Acc), tail(RAList)).
-spec count(ralist(term())) -> non_neg_integer().
count([]) ->
0;
count([{Weight, _Tree} | Rest]) ->
Weight + count(Rest).
% Tree = {Elem} OR {Elem, LTree, RTree}
% RAList = [{Count, Tree}]
-ifdef(EUNIT).
-include_lib("eunit/include/eunit.hrl").
new_test() ->
?assertMatch([], new()).
speed_test() ->
% compare to list
L = lists:seq(1,100),
F = fun() ->
FT = lists:foldl(fun cons/2, new(), L),
L2 = foldl(fun(E,X)->[E|X]end, [], FT)
end,
{Time, L2} = timer:tc(F),
?debugVal(Time),
?assertMatch(L, L2).
lol_test() ->
DataSize = 1000,
QueryCount = 10000,
% compare to list
L = lists:seq(1,DataSize),
RA = lists:foldl(fun cons/2, new(), L),
Times = fun
Foo(0, F) -> ok;
Foo(X, F) -> F(), Foo(X-1, F)
end,
F1 = fun () -> lists:nth(rand:uniform(DataSize), L) end,
F2 = fun () -> lookup(rand:uniform(DataSize)-1, RA) end,
{ListTime, _} = timer:tc(Times, [QueryCount, F1]),
{RATime, _} = timer:tc(Times, [QueryCount, F2]),
?debugVal(ListTime),
?debugVal(RATime).
-endif.