File Coverage

blib/lib/Cuckoo/Filter.pm
Criterion Covered Total %
statement 54 69 78.2
branch 6 10 60.0
condition 3 6 50.0
subroutine 9 10 90.0
pod 5 5 100.0
total 77 100 77.0


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