Current section
Files
Jump to
Current section
Files
src/sets/sbf.erl
%%
%% Copyright (c) 2012 - 2016, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc
%% scalable bloom filter, based on idea discussed in the paper
%% http://gsd.di.uminho.pt/members/cbm/ps/dbloom.pdf
-module(sbf).
-export([
new/1,
new/2,
new/3,
new/4,
add/2,
has/2
]).
%%
%% scalable bloom filter
-record(sbf, {
r :: integer(),
s :: integer(),
size :: integer(),
list :: [_]
}).
%%
%% bloom filter (bf)
%% partition the M bits on k-slices of size m = M/k bits,
%% each slices per hash function.
-record(bf, {
p :: float(),
k :: integer(),
m :: integer(),
n :: integer(),
size :: integer(),
bits :: [_]
}).
%%
%% create new scalable bloom filter
%% C - initial capacity
%% P - false positive probability
%% R - tightening ratio of error probability (as defined by paper)
%% S - growth ratio (as defined by paper)
new(C) ->
new(C, 0.001).
new(C, P) ->
new(C, P, 0.85).
new(C, P, R) ->
new(C, P, R, 1).
new(C, P, R, S) ->
%% n ≈ -m ln (1 - p)
P0 = P * (1 - R),
K = bf_k(P0),
Pk = math:pow(P0, 1 / K),
M = 1 + trunc(log2(-C / math:log(1 - Pk))),
#sbf{r = R, s = S, size = 0, list = [bf_new(M, P0)]}.
%%
%% add element
add(E, #sbf{r = R, s = S, size = Size, list = [H | T]} = State) ->
case has(E, State) of
true ->
State;
false ->
case bf_add(E, H) of
%% filter overflow
#bf{n = N, size = N} = F ->
State#sbf{size = Size + 1, list = [bf_scale(S, R, F), F | T]};
F ->
State#sbf{size = Size + 1, list = [F | T]}
end
end.
%%
%% check membership
has(E, #sbf{list = List}) ->
lists:any(fun(X) -> bf_has(E, X) end, List).
%%%------------------------------------------------------------------
%%%
%%% bloom filter
%%%
%%%------------------------------------------------------------------
%% M - segment modulo
%% P - desired error probability
bf_new(M, P) ->
K = bf_k(P),
Pk= math:pow(P, 1 / K),
N = trunc(-(1 bsl M) * math:log(1 - Pk)),
#bf{
p = P,
k = K,
m = M,
n = N,
size = 0,
bits = [bits_new(1 bsl M) || _ <- lists:seq(1, K)]
}.
%% number of hash functions with 50% fill rate (optimal rate)
bf_k(P) ->
1 + erlang:trunc(log2(1 / P)).
%%
%% insert element to set
bf_add(E, #bf{m = M, k = K, size = Size, bits = Bits0} = State) ->
Mask = 1 bsl M - 1,
Hash = hashes(E, Mask, K),
{Bool, Bits} = lists:unzip([bits_set(H, B) || {H, B} <- lists:zip(Hash, Bits0)]),
case lists:all(fun(X) -> not X end, Bool) of
true ->
State;
false ->
State#bf{size = Size + 1, bits = Bits}
end.
%%
%% lookup element membership
bf_has(E, #bf{m = M, k = K, bits = Bits0}) ->
Mask = 1 bsl M - 1,
Hash = hashes(E, Mask, K),
Bool = [bits_get(H, B) || {H, B} <- lists:zip(Hash, Bits0)],
lists:all(fun(X) -> X end, Bool).
%% The SBF starts with one filter with k0 slices and error probability P0.
%% When this filter gets full, a new one is added with k1 slices and
%% P1 = P0 * r error probability, where r is the tightening ratio with 0 < r < 1.
%% k0 = log2 P0 ^ −1
%% ki = log2 Pi ^ −1
bf_scale(S, R, #bf{p = P, m = M}) ->
bf_new(M + S, P * R).
%%%------------------------------------------------------------------
%%%
%%% bits set
%%%
%%%------------------------------------------------------------------
-define(WORD, 128). % tuned for space / performance
bits_new(N) ->
array:new(1 + N div ?WORD, [{default, 0}]).
bits_set(I, Bits) ->
Cell = I div ?WORD,
Word = array:get(Cell, Bits),
case (Word band (1 bsl (I rem ?WORD))) of
0 ->
{true, array:set(Cell, (Word bor (1 bsl (I rem ?WORD))), Bits)};
_ ->
{false, Bits}
end.
bits_get(I, Bits) ->
Cell = I div ?WORD,
Word = array:get(Cell, Bits),
case (Word band (1 bsl (I rem ?WORD))) of
0 ->
false;
_ ->
true
end.
%%%------------------------------------------------------------------
%%%
%%% private
%%%
%%%------------------------------------------------------------------
%%
log2(X) ->
math:log(X) / math:log(2).
%%
%% calculates K hashes
%% double hashing technique is defined at
%% http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/rsa.pdf
-define(HASH1(X), erlang:phash2([X], 1 bsl 32)).
-define(HASH2(X), erlang:phash2({X}, 1 bsl 32)).
hashes(X, Mask, K) ->
hashes(?HASH1(X), ?HASH2(X), Mask, K).
hashes(_, _, _, 0) ->
[];
hashes(A, B, Mask, K) ->
X = (A + B) band Mask,
[ X | hashes(A, X, Mask, K - 1) ].