File Coverage

blib/lib/Cuckoo/Filter.pm
Criterion Covered Total %
statement 53 68 77.9
branch 4 8 50.0
condition 3 6 50.0
subroutine 9 10 90.0
pod 5 5 100.0
total 74 97 76.2


line stmt bran cond sub pod time code
1             package Cuckoo::Filter;
2              
3 2     2   75056 use warnings;
  2         10  
  2         71  
4 2     2   11 use strict;
  2         5  
  2         78  
5              
6             our $VERSION = "0.0.3";
7              
8 2     2   833 use Digest;
  2         1234  
  2         1395  
9              
10             sub new {
11 1     1 1 101 my ($class, %params) = @_;
12 1         10 my $self = {
13             bucket_size => 2 ** 18,
14             max_retry => 500,
15             fingerprint_size => 3,
16             %params,
17             item_count => 0,
18             };
19 1   33     14 $self->{digest} //= Digest->new("SHA-1");
20             # init fixed array
21 1         3286 $self->{buckets} = do {
22 1         3 my @buckets = ();
23 1         1206 $#buckets = $self->{bucket_size};
24             \@buckets
25 1         10 };
26              
27 1         10 return bless $self, $class;
28             }
29              
30             sub _fingerprint {
31 11     11   20 my ($self, $item) = @_;
32 11         20 my $digest = do {
33 11         23 my $d= $self->{digest};
34 11         40 $d->add($item);
35 11         59 $d->digest;
36             };
37              
38 11         35 return substr($digest, 0, $self->{fingerprint_size}-1);
39             }
40              
41             # djb hash
42             sub _hash {
43 22     22   37 my $str = shift;
44 22         49 my @bytes = unpack 'C*', $str;
45 22         30 my $h = 5381;
46 22         35 for my $i (@bytes) {
47 33         51 $h = (($h << 5) + $h) + $i;
48             }
49 22         48 $h;
50             }
51              
52             sub lookup {
53 7     7 1 13 my ($self, $item) = @_;
54 7         18 my $fp = $self->_fingerprint($item);
55 7         14 my $idx1 = _hash($item) % $self->{bucket_size};
56 7         11 my $idx2 = ($idx1 ^ _hash($fp)) % $self->{bucket_size};
57              
58 7   66     62 return defined $self->{buckets}[$idx1] || $self->{buckets}[$idx2];
59             }
60              
61             sub insert {
62 4     4 1 19 my ($self, $item) = @_;
63 4 100       13 return 0 if $self->lookup($item);
64 3         8 my $fp = $self->_fingerprint($item);
65 3         7 my $idx1 = _hash($item) % $self->{bucket_size};
66 3         6 my $idx2 = ($idx1 ^ _hash($fp)) % $self->{bucket_size};
67 3         4 for my $index ($idx1, $idx2) {
68 3 50       8 if (! defined $self->{buckets}[$index]) {
69 3         4 $self->{buckets}[$index] = $item;
70 3         5 $self->{item_count}++;
71 3         15 return 1;
72             }
73             }
74              
75 0         0 my $index = +($idx1, $idx2)[int(rand(2))];
76 0         0 for (my $i = 0; $i < $self->{max_retry}; $i++) {
77 0         0 $fp = do {
78 0         0 my $f = $self->{buckets}[$index];
79 0         0 $self->{buckets}[$index] = $fp;
80 0         0 $f;
81             };
82 0         0 $index = ($idx1 ^ _hash($fp)) % $self->{bucket_size};
83              
84 0 0       0 if (! defined $self->{buckets}[$index]) {
85 0         0 $self->{buckets}[$index] = $fp;
86 0         0 $self->{item_count}++;
87 0         0 return 1;
88             }
89             }
90              
91 0         0 return 0;
92             }
93              
94             sub delete {
95 1     1 1 5 my ($self, $item) = @_;
96 1         4 my $fp = $self->_fingerprint($item);
97 1         4 my $idx1 = _hash($item) % $self->{bucket_size};
98 1         5 my $idx2 = ($idx1 ^ _hash($fp)) % $self->{bucket_size};
99 1         4 for my $index ($idx1, $idx2) {
100 1 50       6 if (defined $self->{buckets}[$index]) {
101 1         3 delete $self->{buckets}[$index];
102 1         3 $self->{item_count}--;
103 1         7 return 1;
104             }
105             }
106 0           return 0;
107             }
108              
109             sub count {
110 0     0 1   my $self = shift;
111 0           return $self->{item_count};
112             }
113              
114             1;
115             __END__