use strict; use integer; use warnings; package BitField; use Digest::MD5 (); sub new { my $class = shift; my $arg = shift || {}; my %self; $self{bits} = $arg->{bits} || 307; $self{bucketsize} = $arg->{bucketsize} || 31; if ($arg->{buckets}) { $self{buckets} = $arg->{buckets}; } else { my $nbuckets = int($self{bits} / $self{bucketsize}); $self{buckets} = []; do { push @{$self{buckets}}, 0; } while ($nbuckets--); } return bless \%self, $class; } sub setbit { my $self = shift; my $bn = int($_[0] / $self->{bucketsize}); my $bit = $_[0] % $self->{bucketsize}; $self->{buckets}[$bn] |= (1 << $bit); } sub bitset { my $self = shift; my $bn = int($_[0] / $self->{bucketsize}); my $bit = $_[0] % $self->{bucketsize}; return $self->{buckets}[$bn] & (1 << $bit); } package BloomFilter; use Carp; sub new { my $class = shift; my $arg = shift || croak __PACKAGE__.'->new() missing argument hash'; my %self; $self{salts} = $arg->{salts} || croak 'missing salts argument'; $self{bits} = $arg->{bits} || 200; $self{bucketsize} = $arg->{bucketsize} || 31; my %bfarg = ( bits => $self{bits}, bucketsize => $self{bucketsize} ); $bfarg{buckets} = $arg->{buckets} if $arg->{buckets}; $self{bitfield} = BitField->new(\%bfarg); return bless \%self, $class; } sub hashes { my $self = shift; my $v = shift; my @r; for (@{$self->{salts}}) { push @r, (hex(substr(Digest::MD5::md5_hex("$_$v"),0,7)) % $self->{bits}); } return @r; } sub add { my $self = shift; my @h = $self->hashes(shift); for (@h) { $self->{bitfield}->setbit($_); } } sub test { my $self = shift; my @h = $self->hashes(shift); for (@h) { next if $self->{bitfield}->bitset($_); return; } return 1; } sub as_JSON { require JSON; my $self = shift; return JSON::objToJson({ buckets => $self->{bitfield}{buckets}, bucketsize => $self->{bucketsize}, bits => $self->{bits}, salts => $self->{salts}, }); } 1;