File Coverage

lib/Algorithm/SlidingWindow.pm
Criterion Covered Total %
statement 102 102 100.0
branch 44 46 95.6
condition n/a
subroutine 14 14 100.0
pod 11 11 100.0
total 171 173 98.8


line stmt bran cond sub pod time code
1             package Algorithm::SlidingWindow;
2              
3 2     2   225055 use strict;
  2         4  
  2         73  
4 2     2   9 use warnings;
  2         3  
  2         133  
5              
6 2     2   11 use Carp 'croak';
  2         3  
  2         2516  
7              
8              
9             our $VERSION = '1.002';
10              
11             sub new {
12 7     7 1 375128 my ($class, %args) = @_;
13              
14             # --- die if extra arguments ---
15 7         17 my %allowed = map { $_ => 1 } qw(capacity on_evict);
  14         36  
16 7         20 for my $k (keys %args) {
17 9 100       113 croak "unknown argument '$k'" unless $allowed{$k};
18             }
19              
20             # --- required arguments ---
21 6         24 my $capacity = $args{capacity};
22 6 100       202 defined $capacity or croak "capacity is required";
23 5 100       106 $capacity =~ /\A[0-9]+\z/ or croak "capacity must be a positive integer";
24 4 100       105 $capacity > 0 or croak "capacity must be > 0";
25              
26             # --- optional arguments ---
27 3         5 my $on_evict = $args{on_evict};
28 3 100       11 if (defined $on_evict) {
29 2 100       101 ref($on_evict) eq 'CODE' or croak "on_evict must be a CODE reference";
30             }
31              
32             # --- initialize backing store ---
33 2         3 my @buf;
34 2         10 $#buf = $capacity - 1; # preallocate fixed storage
35              
36 2         16 my $self = bless {
37             _cap => 0 + $capacity,
38             _buf => \@buf,
39             _head => 0,
40             _size => 0,
41             _on_evict => $on_evict,
42             }, $class;
43              
44 2         9 return $self;
45             }
46              
47             sub add {
48 7     7 1 1482 my $self = $_[0];
49              
50             # Fast path: no items to add
51 7 50       21 return $self if @_ == 1;
52              
53 7         22 my $cap = $self->{_cap};
54 7         10 my $buf = $self->{_buf};
55 7         9 my $head = $self->{_head};
56 7         8 my $size = $self->{_size};
57 7         10 my $cb = $self->{_on_evict};
58              
59 7         22 for (my $ai = 1; $ai < @_; $ai++) {
60 13         14 my $item = $_[$ai];
61              
62 13 100       24 if ($size == $cap) {
63 8         34 my $old = $buf->[$head];
64 8 100       20 $cb->($old) if $cb;
65              
66             # Drop references immediately
67 8         17 $buf->[$head] = undef;
68              
69 8         9 $head++;
70 8 100       19 $head = 0 if $head == $cap;
71             }
72             else {
73 5         7 $size++;
74             }
75              
76 13         16 my $tail = $head + $size - 1;
77 13 100       21 $tail -= $cap if $tail >= $cap;
78              
79 13         26 $buf->[$tail] = $item;
80             }
81              
82 7         9 $self->{_head} = $head;
83 7         11 $self->{_size} = $size;
84              
85 7         10 return $self;
86             }
87              
88             sub values {
89 7     7 1 9 my $self = $_[0];
90              
91 7         10 my $size = $self->{_size};
92 7 100       25 return () if $size == 0;
93              
94 5         7 my $cap = $self->{_cap};
95 5         6 my $buf = $self->{_buf};
96 5         7 my $i = $self->{_head};
97              
98 5         4 my @out;
99 5         13 $#out = $size - 1;
100              
101 5         11 for (my $k = 0; $k < $size; $k++) {
102 13         17 $out[$k] = $buf->[$i];
103 13         11 $i++;
104 13 100       24 $i = 0 if $i == $cap;
105             }
106              
107 5         28 return @out;
108             }
109              
110             sub get {
111 8     8 1 11 my $self = $_[0];
112 8 100       22 return undef if @_ < 2;
113              
114 7         8 my $index = $_[1];
115 7 100       16 return undef if !defined $index;
116 6 100       31 return undef unless $index =~ /\A[0-9]+\z/;
117 4         6 $index = 0 + $index;
118              
119 4         8 my $size = $self->{_size};
120 4 100       11 return undef if $index >= $size;
121              
122 3         5 my $cap = $self->{_cap};
123 3         3 my $buf = $self->{_buf};
124 3         4 my $head = $self->{_head};
125              
126 3         4 my $i = $head + $index;
127 3 100       6 $i -= $cap if $i >= $cap;
128              
129 3         13 return $buf->[$i];
130             }
131              
132             sub clear {
133 2     2 1 815 my $self = $_[0];
134              
135 2         11 my $size = $self->{_size};
136 2 50       10 return $self if $size == 0;
137              
138 2         44 my $cap = $self->{_cap};
139 2         5 my $buf = $self->{_buf};
140 2         4 my $i = $self->{_head};
141              
142 2         10 for (my $k = 0; $k < $size; $k++) {
143 5         34 $buf->[$i] = undef;
144 5         8 $i++;
145 5 100       19 $i = 0 if $i == $cap;
146             }
147              
148 2         5 $self->{_head} = 0;
149 2         4 $self->{_size} = 0;
150              
151 2         7 return $self;
152             }
153              
154 1     1 1 5 sub capacity { $_[0]->{_cap} }
155 5     5 1 20 sub size { $_[0]->{_size} }
156 2     2 1 37 sub is_empty { $_[0]->{_size} == 0 }
157 3     3 1 17 sub is_full { $_[0]->{_size} == $_[0]->{_cap} }
158              
159             sub oldest {
160 5     5 1 9 my $self = $_[0];
161 5 100       18 return undef if $self->{_size} == 0;
162 3         12 return $self->{_buf}[ $self->{_head} ];
163             }
164              
165             sub newest {
166 5     5 1 10 my $self = $_[0];
167              
168 5         9 my $size = $self->{_size};
169 5 100       17 return undef if $size == 0;
170              
171 3         4 my $cap = $self->{_cap};
172 3         4 my $head = $self->{_head};
173              
174 3         4 my $i = $head + $size - 1;
175 3 100       7 $i -= $cap if $i >= $cap;
176              
177 3         10 return $self->{_buf}[$i];
178             }
179              
180             1;
181              
182             __END__