File Coverage

blib/lib/Algorithm/HyperLogLog/PP.pm
Criterion Covered Total %
statement 73 76 96.0
branch 18 22 81.8
condition 6 6 100.0
subroutine 14 15 93.3
pod 0 6 0.0
total 111 125 88.8


line stmt bran cond sub pod time code
1             package Algorithm::HyperLogLog::PP;
2 5     5   1433 use strict;
  5         13  
  5         170  
3 5     5   32 use warnings;
  5         32  
  5         129  
4 5     5   83 use 5.008008;
  5         22  
5 5     5   33 use Carp ();
  5         11  
  5         124  
6 5     5   2572 use Digest::MurmurHash3::PurePerl qw(murmur32);
  5         10480  
  5         397  
7             use constant {
8 5         4223 HLL_HASH_SEED => 313,
9             TWO_32 => 4294967296.0,
10             NEG_TWO_32 => -4294967296.0,
11 5     5   39 };
  5         14  
12              
13             our $VERSION = "0.24";
14              
15             require Algorithm::HyperLogLog;
16              
17             {
18              
19             package Algorithm::HyperLogLog;
20             our @ISA = qw(Algorithm::HyperLogLog::PP);
21             }
22              
23             sub new {
24 112     112 0 327905 my ( $class, $k ) = @_;
25              
26 112 100 100     1012 if ( $k < 4 || $k > 16 ) {
27 2         291 Carp::croak "Number of ragisters must be in the range [4,16]";
28             }
29              
30 110         326 my $m = 1 << $k;
31 110         114989 my $registers = [ (0) x $m ];
32 110         618 my $alpha = 0;
33 110 100       704 if ( $m == 16 ) {
    100          
    50          
34 1         17 $alpha = 0.673;
35             }
36             elsif ( $m == 32 ) {
37 3         8 $alpha = 0.697;
38             }
39             elsif ( $m == 64 ) {
40 0         0 $alpha = 0.709;
41             }
42             else {
43 106         838 $alpha = 0.7213 / ( 1.0 + 1.079 / $m );
44             }
45              
46 110         817 my $self = {
47             k => $k,
48             m => $m,
49             registers => $registers,
50             alphaMM => $alpha * $m * $m,
51             };
52 110         401 bless $self, $class;
53 110         1578 return $self;
54             }
55              
56             sub _new_from_dump {
57 2     2   8 my ( $class, $k, $data ) = @_;
58 2         7 my $self = $class->new($k);
59 2         5 $self->{registers} = $data;
60 2         636 return $self;
61             }
62              
63             sub _dump_register {
64 2     2   5 my $self = shift;
65 2         6 return $self->{registers};
66             }
67              
68             sub register_size {
69 7     7 0 28 my $self = shift;
70 7         71 return $self->{m};
71             }
72              
73             sub add {
74 500104     500104 0 8023674 my ( $self, @data_list ) = @_;
75 500104         911450 for my $data (@data_list) {
76 600004         1354684 my $hash = murmur32( $data, HLL_HASH_SEED );
77 600004         42254771 my $index = ( $hash >> ( 32 - $self->{'k'} ) );
78 600004         1321169 my $rank = _rho( ( $hash << $self->{k} ), 32 - $self->{k} );
79 600004 100       1799874 if ( $rank > $self->{registers}[$index] ) {
80 295197         700814 $self->{registers}[$index] = $rank;
81             }
82             }
83             }
84              
85             sub estimate {
86 205     205 0 97507 my $self = shift;
87 205         620 my $m = $self->{m};
88              
89 205         481 my $rank = 0;
90 205         477 my $sum = 0.0;
91 205         821 for my $i ( 0 .. ( $m - 1 ) ) {
92 13303872         20429385 $rank = $self->{registers}[$i];
93 13303872         21556545 $sum += 1.0 / ( 2.0**$rank );
94             }
95              
96 205         909 my $estimate = $self->{alphaMM} * ( 1.0 / $sum ); # E in the original paper
97 205 100       1007 if ( $estimate <= 2.5 * $m ) {
    50          
98 204         469 my $v = 0;
99 204         996 for my $i ( 0 .. ( $m - 1 ) ) {
100 13238336 100       28273170 if ( $self->{registers}[$i] == 0 ) {
101 9917587         15682287 $v++;
102             }
103             }
104              
105 204 50       776 if ( $v != 0 ) {
106 204         1687 $estimate = $m * log( $m / $v );
107             }
108             }
109             elsif ( $estimate > ( 1.0 / 30.0 ) * TWO_32 ) {
110 0         0 $estimate = NEG_TWO_32 * log( 1.0 - ( $estimate / TWO_32 ) );
111             }
112 205         21614 return $estimate;
113             }
114              
115             sub merge {
116 1     1 0 8 my ($self, $other) = @_;
117 1         3 my $m = $self->{m};
118              
119 1 50       5 die "hll size misatch" if $self->{m} != $other->{m};
120              
121 1         4 for (my $i=0; $i<$m; $i++) {
122 65536 100       156823 if ($self->{registers}[$i] < $other->{registers}[$i]) {
123 26799         58427 $self->{registers}[$i] = $other->{registers}[$i];
124             }
125             }
126             }
127              
128             sub XS {
129 0     0 0 0 0;
130             }
131              
132             sub _rho {
133 600004     600004   1085794 my ( $x, $b ) = @_;
134 600004         928579 my $v = 1;
135 600004   100     2462910 while ( $v <= $b && !( $x & 0x80000000 ) ) {
136 499896         775815 $v++;
137 499896         1808481 $x <<= 1;
138             }
139 600004         1083113 return $v;
140             }
141              
142             1;
143             __END__