File Coverage

blib/lib/Algorithm/HyperLogLog/PP.pm
Criterion Covered Total %
statement 74 77 96.1
branch 18 22 81.8
condition 6 6 100.0
subroutine 14 15 93.3
pod 0 6 0.0
total 112 126 88.8


line stmt bran cond sub pod time code
1             package Algorithm::HyperLogLog::PP;
2 5     5   2045 use strict;
  5         11  
  5         190  
3 5     5   28 use warnings;
  5         34  
  5         130  
4 5     5   102 use 5.008008;
  5         16  
  5         226  
5 5     5   25 use Carp ();
  5         8  
  5         116  
6 5     5   5590 use Digest::MurmurHash3::PurePerl qw(murmur32);
  5         15768  
  5         438  
7             use constant {
8 5         4746 HLL_HASH_SEED => 313,
9             TWO_32 => 4294967296.0,
10             NEG_TWO_32 => -4294967296.0,
11 5     5   39 };
  5         10  
12              
13             our $VERSION = "0.23";
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 481661 my ( $class, $k ) = @_;
25              
26 112 100 100     1234 if ( $k < 4 || $k > 16 ) {
27 2         583 Carp::croak "Number of ragisters must be in the range [4,16]";
28             }
29              
30 110         296 my $m = 1 << $k;
31 110         342569 my $registers = [ (0) x $m ];
32 110         454 my $alpha = 0;
33 110 100       971 if ( $m == 16 ) {
    100          
    50          
34 1         3 $alpha = 0.673;
35             }
36             elsif ( $m == 32 ) {
37 3         9 $alpha = 0.697;
38             }
39             elsif ( $m == 64 ) {
40 0         0 $alpha = 0.709;
41             }
42             else {
43 106         495 $alpha = 0.7213 / ( 1.0 + 1.079 / $m );
44             }
45              
46 110         1331 my $self = {
47             k => $k,
48             m => $m,
49             registers => $registers,
50             alphaMM => $alpha * $m * $m,
51             };
52 110         666 bless $self, $class;
53 110         2480 return $self;
54             }
55              
56             sub _new_from_dump {
57 2     2   7 my ( $class, $k, $data ) = @_;
58 2         10 my $self = $class->new($k);
59 2         7 $self->{registers} = $data;
60 2         1038 return $self;
61             }
62              
63             sub _dump_register {
64 2     2   35 my $self = shift;
65 2         10 return $self->{registers};
66             }
67              
68             sub register_size {
69 7     7 0 40 my $self = shift;
70 7         79 return $self->{m};
71             }
72              
73             sub add {
74 500104     500104 0 6916940 my ( $self, @data_list ) = @_;
75 500104         830424 for my $data (@data_list) {
76 600004         1541545 my $hash = murmur32( $data, HLL_HASH_SEED );
77 600004         33918244 my $index = ( $hash >> ( 32 - $self->{'k'} ) );
78 600004         1461563 my $rank = _rho( ( $hash << $self->{k} ), 32 - $self->{k} );
79 600004 100       2396212 if ( $rank > $self->{registers}[$index] ) {
80 295371         1083953 $self->{registers}[$index] = $rank;
81             }
82             }
83             }
84              
85             sub estimate {
86 205     205 0 202694 my $self = shift;
87 205         573 my $m = $self->{m};
88              
89 205         400 my $rank = 0;
90 205         428 my $sum = 0.0;
91 205         678 for my $i ( 0 .. ( $m - 1 ) ) {
92 13303872         15207440 $rank = $self->{registers}[$i];
93 13303872         16911816 $sum += 1.0 / ( 2.0**$rank );
94             }
95              
96 205         1505 my $estimate = $self->{alphaMM} * ( 1.0 / $sum ); # E in the original paper
97 205 100       1237 if ( $estimate <= 2.5 * $m ) {
    50          
98 204         419 my $v = 0;
99 204         1098 for my $i ( 0 .. ( $m - 1 ) ) {
100 13238336 100       25243534 if ( $self->{registers}[$i] == 0 ) {
101 9933197         10718756 $v++;
102             }
103             }
104              
105 204 50       1209 if ( $v != 0 ) {
106 204         2014 $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         35157 return $estimate;
113             }
114              
115             sub merge {
116 1     1 0 7 my ($self, $other) = @_;
117 1         3 my $m = $self->{m};
118              
119 1 50       6 die "hll size misatch" if $self->{m} != $other->{m};
120              
121 1         5 for (my $i=0; $i<$m; $i++) {
122 65536 100       168988 if ($self->{registers}[$i] < $other->{registers}[$i]) {
123 26799         61194 $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   766509 my ( $x, $b ) = @_;
134 600004         697825 my $v = 1;
135 600004   100     2747558 while ( $v <= $b && !( $x & 0x80000000 ) ) {
136 499096         550226 $v++;
137 499096         1957922 $x <<= 1;
138             }
139 600004         971647 return $v;
140             }
141              
142             1;
143             __END__