Current section

Files

Jump to
fixpoint src bitmap.erl
Raw

src/bitmap.erl

%%%-------------------------------------------------------------------
%%% @author Heinz N. Gies <heinz@licenser.net>
%%% @copyright (C) 2016, Heinz N. Gies
%%% @doc Library for dealing with bitmaps in erlang.
%%%
%%% @end
%%% Created : 5 Dec 2016 by Heinz N. Gies <heinz@licenser.net>
%%%-------------------------------------------------------------------
-module(bitmap).
%% API exports
-export([
new/1,
from_list/2,
to_list/1,
from_flags/2,
from_flags/3,
to_flags/2,
union/2,
intersection/2,
invert/1,
set/2,
set_many/2,
unset/2,
test/2,
diff/2,
size/1,
bytes/1,
display_diff/3,
display/2
]).
%% Options only valid for newly created bitmaps
-type create_opt() ::
full.
%% options valid for other (i.e. converted) bitmaps
-type general_opt() ::
{size, pos_integer()}.
-type flags() :: [atom()].
-type bitmap() :: <<_:64, _:_*8>>.
-type diff_set() :: [pos_integer()].
-type diff() :: {OnlyA :: diff_set(), OnlyB :: diff_set()}.
%%====================================================================
%% API functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Generates a new bitmap with the given options. The total size
%% will always be a multiople of 8, prefixed with a size..
%%
%% possible options are:
%% - full - the bitmap gets created with all bit s set.
%% - {size, Size} - Size of the bitmap (required)
%% @end
%%--------------------------------------------------------------------
-spec new([create_opt() | general_opt()]) ->
{ok, bitmap()} |
{error, size_needed | bad_size}.
new(Opts) ->
%% bytes gives us the total bytes of a bitmap, including the
%% 8 byte size prefix, so we need to substract that.
R1 = case proplists:get_value(size, Opts) of
undefined ->
{error, size_needed};
Size when Size =< 0 ->
{error, bad_size};
Size ->
Bytes = bitmap:bytes(Size) - 8,
Bits = Bytes * 8,
{ok, <<Size:64/unsigned, 0:Bits/unsigned>>}
end,
case {proplists:get_bool(full, Opts), R1} of
{false, R1} ->
R1;
{true, {ok, B}} ->
{ok, invert(B)};
{_, R1} ->
R1
end.
%%--------------------------------------------------------------------
%% @doc Creates a bitmap from a list of set bits.
%% @end
%%--------------------------------------------------------------------
-spec from_list([non_neg_integer()], [general_opt()]) ->
{ok, bitmap()} |
{error, size_needed | bad_size}.
from_list(Elements, Opts) ->
Size = proplists:get_value(size, Opts),
case lists:sort(Elements) of
[Min | _] = Sorted ->
Max = lists:last(Sorted),
from_list_(Size, Min, Max, Sorted);
Sorted ->
from_list_(Size, 0, 0, Sorted)
end.
from_list_(undefined, _Min, _Max, _Sorted) ->
{error, size_needed};
from_list_(Size, Min, Max, Sorted)
when Sorted =/= []
andalso (Max < 0 orelse Min < 0 orelse Max > Size) ->
{error, bad_points};
from_list_(Size, _, _, Sorted) ->
R = from_list_(0, Sorted, <<>>),
Bytes = bitmap:bytes(Size) - 8,
Bits = Bytes * 8,
Missing = Bits - bit_size(R),
{ok, <<Size:64/unsigned, R/bitstring, 0:Missing>>}.
%%--------------------------------------------------------------------
%% @doc Creates a bitmap to a list of set bits.
%% @end
%%--------------------------------------------------------------------
to_list(<<Size:64/unsigned, Bitmap:Size/bitstring, _/bitstring>>) ->
to_list(Bitmap, 0, []).
to_list(<<>>, _, Acc) ->
lists:reverse(Acc);
to_list(<<0:1, R/bitstring>>, N, Acc) ->
to_list(R, N + 1, Acc);
to_list(<<1:1, R/bitstring>>, N, Acc) ->
to_list(R, N + 1, [N | Acc]).
%%--------------------------------------------------------------------
%% @doc Converts a bitmap into flags given a set of flag options.
%% @end
%%--------------------------------------------------------------------
-spec to_flags(bitmap(), flags()) -> flags().
to_flags(Bitmap = <<Size:64/unsigned, _/bitstring>>, Flags)
when length(Flags) =< Size ->
[lists:nth(N + 1, Flags) || N <- to_list(Bitmap)].
%%--------------------------------------------------------------------
%% @doc Creates a new bitmap from a set of flags and a list of all
%% available flags. The size of the resulting bitmap equals the
%% total number of flags.
%% @end
%%--------------------------------------------------------------------
from_flags(Set, Flags) ->
Size = length(Flags),
from_flags(Set, Flags, [{size, Size}]).
%%--------------------------------------------------------------------
%% @doc Creates a new bitmap from a set of flags and a list of all
%% available flags. With custom options.
%% @end
%%--------------------------------------------------------------------
from_flags(Set, Flags, Opts) ->
Map = flag_map(Flags, 0, #{}),
from_list([maps:get(F, Map) || F <- Set], Opts).
%%--------------------------------------------------------------------
%% @doc Inverts all set items on the bitmap
%% @end
%%--------------------------------------------------------------------
-spec invert(bitmap()) -> bitmap().
invert(<<Size:64/unsigned, V:Size/unsigned, P/bitstring>>) ->
<<Size:64/unsigned, (bnot V):Size/unsigned, P/bitstring>>.
%%--------------------------------------------------------------------
%% @doc Sets a position in the bitmap.
%% @end
%%--------------------------------------------------------------------
-spec set(Position :: non_neg_integer(), bitmap()) ->
{ok, bitmap()}.
set(Position, <<Size:64/unsigned, Bitmap/binary>>)
when Position >= 0,
Position < Size ->
<<Head:Position/bitstring, _:1, Tail/bitstring>> = Bitmap,
Bitmap1 = <<Size:64/unsigned,
Head/bitstring,
1:1,
Tail/bitstring>>,
{ok, Bitmap1}.
%%--------------------------------------------------------------------
%% @doc Sets many psoitions in a bitmap (significantly faster then
%% using set multiple times!)
%% @end
%%--------------------------------------------------------------------
-spec set_many([non_neg_integer()], bitmap()) ->
{ok, bitmap()}.
set_many(Positions, <<Size:64/unsigned, _/binary>> = Bitmap) ->
{ok, Mask} = from_list(Positions, [{size, Size}]),
{ok, union(Bitmap, Mask)}.
%%--------------------------------------------------------------------
%% @doc Unsets a position in the bitmap.
%% @end
%%--------------------------------------------------------------------
-spec unset(Position :: pos_integer(), bitmap()) ->
{ok, bitmap()}.
unset(Position, <<Size:64/unsigned, Bitmap/binary>>)
when Position >= 0,
Position < Size ->
<<Head:Position/bitstring, _:1, Tail/bitstring>> = Bitmap,
Bitmap1 = <<Size:64/unsigned,
Head/bitstring,
0:1,
Tail/bitstring>>,
{ok, Bitmap1}.
%%--------------------------------------------------------------------
%% @doc Tests weather a position is set in the bitmap.
%% @end
%%--------------------------------------------------------------------
-spec test(Position :: pos_integer(), bitmap()) ->
boolean() |
{error, out_of_range}.
test(Position, <<Size:64/unsigned, Bitmap/binary>>)
when Position >= 0,
Position < Size ->
<<_Head:Position/bitstring, R:1, _Tail/bitstring>> = Bitmap,
R =:= 1.
%%--------------------------------------------------------------------
%% @doc Returns a diff of bitmaps or an error if they have a different
%% size.
%% @end
%%--------------------------------------------------------------------
-spec diff(bitmap(), bitmap()) ->
{ok, diff()} |
{error, bad_size}.
diff(Bitmap, Bitmap) ->
{ok, {[], []}};
diff(<<Size:64/unsigned, BitmapL/binary>>,
<<Size:64/unsigned, BitmapR/binary>>) ->
diff_(0, BitmapL, BitmapR, [], []).
diff_(_N, _Bitmap, _Bitmap, L, R) ->
{ok, {lists:reverse(L), lists:reverse(R)}};
diff_(N,
<<X:1, BitmapL/bitstring>>,
<<X:1, BitmapR/bitstring>>, L, R) ->
diff_(N + 1, BitmapL, BitmapR, L, R);
diff_(N,
<<1:1, BitmapL/bitstring>>,
<<0:1, BitmapR/bitstring>>, L, R) ->
diff_(N + 1, BitmapL, BitmapR, [N | L], R);
diff_(N,
<<0:1, BitmapL/bitstring>>,
<<1:1, BitmapR/bitstring>>, L, R) ->
diff_(N + 1, BitmapL, BitmapR, L, [N | R]).
%%--------------------------------------------------------------------
%% @doc Size in bytes the bitmap will take up.
%% @end
%%--------------------------------------------------------------------
-spec bytes(pos_integer()) ->
pos_integer().
bytes(Size) ->
ceiling(Size / 8) + 8.
%%--------------------------------------------------------------------
%% @doc returns the size of a bitmap.,
%% @end
%%--------------------------------------------------------------------
-spec size(bitmap()) ->
pos_integer().
size(<<Size:64/unsigned, _/binary>>) ->
Size.
%%--------------------------------------------------------------------
%% @doc Visualizes the difference between to bitmaps.
%% @end
%%--------------------------------------------------------------------
display_diff(<<Size:64, _/binary>> = LB, <<Size:64, _/binary>> = RB, Width) ->
{ok, {L, R}} = diff(LB, RB),
D = diff_view(Size, L, R),
print_grid(D, Width).
%%--------------------------------------------------------------------
%% @doc Visualizes a bitmap.
%% @end
%%--------------------------------------------------------------------
display(<<Size:64, X/binary>>, Width) ->
{V, _} = lists:split(Size, to_view(X, [])),
print_grid(V, Width).
%%--------------------------------------------------------------------
%% @doc Creates the intersection of two bitmaps (binary and)
%% @end
%%--------------------------------------------------------------------
-spec intersection(bitmap(), bitmap()) -> bitmap().
intersection(<<Size:64/unsigned, L:Size/unsigned, P/bitstring>>,
<<Size:64/unsigned, R:Size/unsigned, _/bitstring>>) ->
<<Size:64/unsigned, (L band R):Size/unsigned, P/bitstring>>.
%%--------------------------------------------------------------------
%% @doc Creates the union of two bitmaps (binary or)
%% @end
%%--------------------------------------------------------------------
-spec union(bitmap(), bitmap()) -> bitmap().
union(<<Size:64/unsigned, L:Size/unsigned, P/bitstring>>,
<<Size:64/unsigned, R:Size/unsigned, _/bitstring>>) ->
<<Size:64/unsigned, (L bor R):Size/unsigned, P/bitstring>>.
%%====================================================================
%% Internal functions
%%====================================================================
diff_view(Size, L, R) ->
diff_to_view(0, Size, L, R, []).
diff_to_view(_Size, _Size, _L, _R, Acc) ->
lists:reverse(Acc);
diff_to_view(P, Size, [P | L], R, Acc) ->
diff_to_view(P + 1, Size, L, R, [cf:format("~!r<") | Acc]);
diff_to_view(P, Size, L, [P | R], Acc) ->
diff_to_view(P + 1, Size, L, R, [cf:format("~!r>") | Acc]);
diff_to_view(P, Size, L, R, Acc) ->
diff_to_view(P + 1, Size, L, R, [cf:format("~!g*") | Acc]).
to_view(<<>>, Acc) ->
lists:reverse(Acc);
to_view(<<1:1, R/bitstring>>, Acc) ->
to_view(R, [cf:format("~!g*") | Acc]);
to_view(<<0:1, R/bitstring>>, Acc) ->
to_view(R, [cf:format("~!y_") | Acc]).
print_grid(List, Width) ->
Log = trunc(math:log10(length(List))) + 1,
Space = integer_to_list(Log),
header(Space, Width),
S = "~n~" ++ Space ++ "b ~s",
print_grid(S, List, 0, Width).
print_grid(_S, [], _N, _Count) ->
ok;
print_grid(S, List, N, Count) when length(List) > Count ->
{H, T} = lists:split(Count, List),
io:format(S, [Count * N, H]),
print_grid(S, T, N + 1, Count);
print_grid(S, List, N, Count) ->
io:format(S, [Count * N, List]).
header(Space, Width) ->
Log = trunc(math:log10(Width - 1)),
Pfx = io_lib:format("~" ++ Space ++ "c", [$\s]),
Idx = lists:seq(0, Width-1),
print_hdrs(Log, Idx, Pfx).
print_hdrs(0, Idx, Pfx) ->
io:format("~s ~s", [Pfx, [integer_to_list(X rem 10) || X <- Idx]]);
print_hdrs(N, Idx, Pfx) ->
io:format("~s ~s~n", [Pfx, remove_zero([to_s(N, X) || X <- Idx])]),
print_hdrs(N - 1, Idx, Pfx).
to_s(N, X) ->
R = (X div round(math:pow(10, N))) rem 10,
integer_to_list(R).
remove_zero(["0" | R]) ->
[$\s | remove_zero(R)];
remove_zero(R) ->
R.
ceiling(X) when X < 0 ->
trunc(X);
ceiling(X) ->
T = trunc(X),
case X - T == 0 of
true -> T;
false -> T + 1
end.
from_list_(Pos, [X | R], Acc) when X > Pos ->
Missing = X - Pos,
from_list_(X + 1, R, <<Acc/bitstring, 0:Missing, 1:1>>);
from_list_(Pos, [Pos | R], Acc) ->
from_list_(Pos + 1, R, <<Acc/bitstring, 1:1>>);
from_list_(_, [], R) ->
R.
flag_map([], _I, Map) ->
Map;
flag_map([K | R], I, Map) ->
flag_map(R, I + 1, maps:put(K, I, Map)).