File Coverage

blib/lib/Statistics/TopK.pm
Criterion Covered Total %
statement 49 51 96.0
branch 7 8 87.5
condition 2 6 33.3
subroutine 11 12 91.6
pod 4 4 100.0
total 73 81 90.1


line stmt bran cond sub pod time code
1             package Statistics::TopK;
2              
3 3     3   83095 use strict;
  3         8  
  3         130  
4 3     3   16 use warnings;
  3         7  
  3         115  
5              
6 3     3   17 use Carp qw(croak);
  3         11  
  3         399  
7              
8             our $VERSION = '0.01';
9             $VERSION = eval $VERSION;
10              
11 3     3   30 use constant _K => 0;
  3         5  
  3         268  
12 3     3   15 use constant _COUNTS => 1;
  3         6  
  3         132  
13 3     3   15 use constant _ELEMS => 2;
  3         14  
  3         116  
14 3     3   16 use constant _SIZE => 3;
  3         10  
  3         144  
15 3     3   14 use constant _INDEX => 4;
  3         6  
  3         1836  
16              
17             sub new {
18 5     5 1 2676 my ($class, $k) = @_;
19              
20 5 50 33     81 croak 'expecting a positive integer'
      33        
21             unless defined $k and $k =~ /^\d+$/ and $k > 0;
22              
23 5         23 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         10 keys %{ $self->[_COUNTS] } = $k;
  5         21  
33 5         14 $#{ $self->[_ELEMS] } = $k - 1;
  5         17  
34              
35 5         20 return bless $self, $class;
36             }
37              
38             sub add {
39 1200     1200 1 3278 my ($self, $elem) = @_;
40              
41             # Increment the element's counter if it is currently being counted.
42 1200 100       2250 if (exists $self->[_COUNTS]{$elem}) {
43 957         8929 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       446 if ($self->[_SIZE] < $self->[_K]) {
48 15         28 $self->[_ELEMS][ $self->[_SIZE]++ ] = $elem;
49 15         46 return $self->[_COUNTS]{$elem} = 1;
50             }
51              
52             # Decrement one of the currently counted elements.
53 228         267 my $index = $self->[_INDEX];
54 228         250 my $prev = $self->[_ELEMS][$index];
55 228         270 my $count = $self->[_COUNTS]{$prev} -= 1;
56              
57             # Advance the counter.
58 228         286 $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       366 if (0 == $count) {
63 103         148 delete $self->[_COUNTS]{$prev};
64              
65 103         115 $self->[_ELEMS][$index] = $elem;
66              
67 103         265 return $self->[_COUNTS]{$elem} = 1;
68             }
69              
70             # This element is not currently being counted.
71 125         237 return 0;
72             }
73              
74             sub top {
75 4     4 1 29 return keys %{$_[0]->[_COUNTS]};
  4         44  
76             }
77              
78             sub counts {
79 0     0 1   return %{$_->[0]->[_COUNTS]};
  0            
80             }
81              
82              
83             1;
84              
85             __END__