File Coverage

blib/lib/Statistics/TopK.pm
Criterion Covered Total %
statement 51 51 100.0
branch 7 8 87.5
condition 2 6 33.3
subroutine 12 12 100.0
pod 4 4 100.0
total 76 81 93.8


line stmt bran cond sub pod time code
1             package Statistics::TopK;
2              
3 3     3   53302 use strict;
  3         5  
  3         108  
4 3     3   11 use warnings;
  3         4  
  3         78  
5              
6 3     3   9 use Carp qw(croak);
  3         13  
  3         244  
7              
8             our $VERSION = '0.02';
9             $VERSION = eval $VERSION;
10              
11 3     3   13 use constant _K => 0;
  3         6  
  3         214  
12 3     3   11 use constant _COUNTS => 1;
  3         2  
  3         108  
13 3     3   11 use constant _ELEMS => 2;
  3         7  
  3         97  
14 3     3   11 use constant _SIZE => 3;
  3         3  
  3         106  
15 3     3   11 use constant _INDEX => 4;
  3         3  
  3         1156  
16              
17             sub new {
18 5     5 1 2088 my ($class, $k) = @_;
19              
20 5 50 33     67 croak 'expecting a positive integer'
      33        
21             unless defined $k and $k =~ /^\d+$/ and $k > 0;
22              
23 5         15 my $self = [
24             $k, # _K
25             {}, # _COUNTS
26             [], # _ELEMS
27             0, # _SIZE
28             0, # _INDEX
29             ];
30              
31             # Pre-extend the internal data structures, just in case $k is large.
32 5         8 keys %{ $self->[_COUNTS] } = $k;
  5         19  
33 5         9 $#{ $self->[_ELEMS] } = $k - 1;
  5         15  
34              
35 5         15 return bless $self, $class;
36             }
37              
38             sub add {
39 1200     1200 1 2459 my ($self, $elem) = @_;
40              
41             # Increment the element's counter if it is currently being counted.
42 1200 100       1814 if (exists $self->[_COUNTS]{$elem}) {
43 957         1221 return $self->[_COUNTS]{$elem} += 1;
44             }
45              
46             # Add the element if it's not being counted and there are free slots.
47 243 100       362 if ($self->[_SIZE] < $self->[_K]) {
48 15         25 $self->[_ELEMS][ $self->[_SIZE]++ ] = $elem;
49 15         40 return $self->[_COUNTS]{$elem} = 1;
50             }
51              
52             # Decrement one of the currently counted elements.
53 228         207 my $index = $self->[_INDEX];
54 228         228 my $prev = $self->[_ELEMS][$index];
55 228         247 my $count = $self->[_COUNTS]{$prev} -= 1;
56              
57             # Advance the counter.
58 228         247 $self->[_INDEX] = ++$self->[_INDEX] % $self->[_K];
59              
60             # If the count of the decremented element reaches 0, replace it with the
61             # current element.
62 228 100       328 if (0 == $count) {
63 103         207 delete $self->[_COUNTS]{$prev};
64              
65 103         137 $self->[_ELEMS][$index] = $elem;
66              
67 103         283 return $self->[_COUNTS]{$elem} = 1;
68             }
69              
70             # This element is not currently being counted.
71 125         147 return 0;
72             }
73              
74             sub top {
75 4     4 1 19 return keys %{$_[0]->[_COUNTS]};
  4         28  
76             }
77              
78             sub counts {
79 1     1 1 1 return %{$_[0]->[_COUNTS]};
  1         7  
80             }
81              
82              
83             1;
84              
85             __END__